@chbo297/infoflow 2026.2.24 → 2026.2.28
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/.claude/settings.local.json +9 -0
- package/CHANGELOG.md +82 -0
- package/openclaw.plugin.json +86 -2
- package/package.json +1 -1
- package/src/accounts.ts +3 -0
- package/src/actions.ts +108 -0
- package/src/bot.ts +359 -47
- package/src/channel.ts +20 -13
- package/src/infoflow-req-parse.ts +17 -35
- package/src/logging.ts +10 -28
- package/src/monitor.ts +7 -15
- package/src/reply-dispatcher.ts +81 -12
- package/src/send.ts +25 -7
- package/src/targets.ts +6 -27
- package/src/types.ts +70 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(git add:*)",
|
|
5
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat: add assistant watchMentions, followUp, replyMode and actions support\n\n- Add watchMentions config for assistant-style replies when watched users are @mentioned\n- Add @mention detection and resolution in group messages \\(user and agent\\)\n- Add followUp window for automatic follow-up replies after bot interaction\n- Add 5 replyMode options: ignore, record, mention-only, mention-and-watch, proactive\n- Add ChannelMessageActionAdapter for LLM-initiated message sends\n- Refactor logging: unified logVerbose\\(\\) and formatInfoflowError\\(\\)\n- Update openclaw.plugin.json config schema with new group config format \\(breaking change\\)\n- Add CHANGELOG.md\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
6
|
+
"Bash(npm publish:*)"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
9
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2026.2.28
|
|
4
|
+
|
|
5
|
+
### 新功能
|
|
6
|
+
|
|
7
|
+
#### 助手模式与 @mention 能力
|
|
8
|
+
|
|
9
|
+
- 支持 `watchMentions` 配置:当群内有人 @了 watchMentions 列表中的用户时,机器人作为该用户的助手自动判断是否代为回复
|
|
10
|
+
- 支持识别群消息中的 @mention(人类用户和机器人),并在回复中正确生成 @mention 内容
|
|
11
|
+
- 新增 `at-agent` 消息类型,支持在回复中 @其他机器人
|
|
12
|
+
|
|
13
|
+
#### Follow-Up 跟进回复
|
|
14
|
+
|
|
15
|
+
- 支持 `followUp` 配置(默认开启):机器人回复后,在 `followUpWindow`(默认 300 秒)时间窗口内,即使未被 @,也会判断后续消息是否是对之前话题的延续并决定是否回复
|
|
16
|
+
- 通过内存 Map 跟踪每个群的最后回复时间
|
|
17
|
+
|
|
18
|
+
#### 回复模式系统(replyMode)
|
|
19
|
+
|
|
20
|
+
新增 5 种群聊回复模式,支持按群独立配置:
|
|
21
|
+
|
|
22
|
+
| 模式 | 行为 |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `ignore` | 直接丢弃,不记录、不思考、不回复 |
|
|
25
|
+
| `record` | 仅记录到会话,不思考、不回复 |
|
|
26
|
+
| `mention-only` | 仅在机器人被直接 @ 时回复 |
|
|
27
|
+
| `mention-and-watch`(默认) | 机器人被 @、或 watchMentions 中的用户被 @、或处于 follow-up 窗口内时回复 |
|
|
28
|
+
| `proactive` | 始终思考并判断是否回复 |
|
|
29
|
+
|
|
30
|
+
#### 消息动作(Actions)
|
|
31
|
+
|
|
32
|
+
- 新增 `ChannelMessageActionAdapter` 实现,支持 LLM Agent 通过 `send` action 主动发送消息
|
|
33
|
+
- 支持 `atAll`、`mentionUserIds`、`media` 等参数
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 重构
|
|
38
|
+
|
|
39
|
+
#### 日志系统统一
|
|
40
|
+
|
|
41
|
+
- 移除 `logInfoflowApiError`,新增统一的 `logVerbose()` 函数
|
|
42
|
+
- 全项目统一使用 `logVerbose()` 替代分散的 verbose 日志判断逻辑
|
|
43
|
+
- 统一使用 `formatInfoflowError()` 格式化错误信息
|
|
44
|
+
- 改进多处错误日志的上下文信息
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### ⚠️ 不兼容变更
|
|
49
|
+
|
|
50
|
+
#### openclaw.plugin.json 配置格式变更
|
|
51
|
+
|
|
52
|
+
群聊配置部分有较大变更,**不兼容以前的格式**。主要变更:
|
|
53
|
+
|
|
54
|
+
- 新增顶层 `replyMode`、`followUp`、`followUpWindow`、`watchMentions` 字段
|
|
55
|
+
- 新增 `groups` 对象,支持按群 ID 独立配置(`replyMode`、`watchMentions`、`followUp`、`followUpWindow`、`systemPrompt`)
|
|
56
|
+
- 新增 `defaultAccount` 字段
|
|
57
|
+
- `accounts` 下每个账号也可嵌套独自的 `groups` 配置
|
|
58
|
+
- 配置解析优先级:群级别 → 账号级别 → 顶层默认值
|
|
59
|
+
|
|
60
|
+
旧的 `requireMention` 字段仍可用(内部自动映射到新的 replyMode),但建议迁移到新的 `replyMode` 配置。
|
|
61
|
+
|
|
62
|
+
新配置示例:
|
|
63
|
+
|
|
64
|
+
```json5
|
|
65
|
+
{
|
|
66
|
+
channels: {
|
|
67
|
+
infoflow: {
|
|
68
|
+
replyMode: "mention-and-watch",
|
|
69
|
+
followUp: true,
|
|
70
|
+
followUpWindow: 300,
|
|
71
|
+
watchMentions: ["alice01", "bob02"],
|
|
72
|
+
groups: {
|
|
73
|
+
"12345": {
|
|
74
|
+
replyMode: "proactive",
|
|
75
|
+
systemPrompt: "你是项目 X 的专家...",
|
|
76
|
+
watchMentions: ["alice01"],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
```
|
package/openclaw.plugin.json
CHANGED
|
@@ -4,6 +4,90 @@
|
|
|
4
4
|
"configSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"properties": {
|
|
7
|
+
"properties": {
|
|
8
|
+
"apiHost": { "type": "string" },
|
|
9
|
+
"enabled": { "type": "boolean" },
|
|
10
|
+
"name": { "type": "string" },
|
|
11
|
+
"checkToken": { "type": "string" },
|
|
12
|
+
"encodingAESKey": { "type": "string" },
|
|
13
|
+
"appKey": { "type": "string" },
|
|
14
|
+
"appSecret": { "type": "string" },
|
|
15
|
+
"robotName": { "type": "string" },
|
|
16
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
17
|
+
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
18
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
19
|
+
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
20
|
+
"requireMention": { "type": "boolean" },
|
|
21
|
+
"replyMode": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"],
|
|
24
|
+
"default": "mention-and-watch"
|
|
25
|
+
},
|
|
26
|
+
"followUp": { "type": "boolean", "default": true },
|
|
27
|
+
"followUpWindow": { "type": "number", "default": 300 },
|
|
28
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
29
|
+
"groups": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"replyMode": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
37
|
+
},
|
|
38
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
39
|
+
"followUp": { "type": "boolean" },
|
|
40
|
+
"followUpWindow": { "type": "number" },
|
|
41
|
+
"systemPrompt": { "type": "string" }
|
|
42
|
+
},
|
|
43
|
+
"additionalProperties": false
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"accounts": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"additionalProperties": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"enabled": { "type": "boolean" },
|
|
52
|
+
"name": { "type": "string" },
|
|
53
|
+
"checkToken": { "type": "string" },
|
|
54
|
+
"encodingAESKey": { "type": "string" },
|
|
55
|
+
"appKey": { "type": "string" },
|
|
56
|
+
"appSecret": { "type": "string" },
|
|
57
|
+
"robotName": { "type": "string" },
|
|
58
|
+
"dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"] },
|
|
59
|
+
"allowFrom": { "type": "array", "items": { "type": "string" } },
|
|
60
|
+
"groupPolicy": { "type": "string", "enum": ["open", "allowlist", "disabled"] },
|
|
61
|
+
"groupAllowFrom": { "type": "array", "items": { "type": "string" } },
|
|
62
|
+
"requireMention": { "type": "boolean" },
|
|
63
|
+
"replyMode": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
66
|
+
},
|
|
67
|
+
"followUp": { "type": "boolean" },
|
|
68
|
+
"followUpWindow": { "type": "number" },
|
|
69
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
70
|
+
"groups": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"additionalProperties": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"replyMode": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": ["ignore", "record", "mention-only", "mention-and-watch", "proactive"]
|
|
78
|
+
},
|
|
79
|
+
"watchMentions": { "type": "array", "items": { "type": "string" } },
|
|
80
|
+
"followUp": { "type": "boolean" },
|
|
81
|
+
"followUpWindow": { "type": "number" },
|
|
82
|
+
"systemPrompt": { "type": "string" }
|
|
83
|
+
},
|
|
84
|
+
"additionalProperties": false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"defaultAccount": { "type": "string" }
|
|
91
|
+
}
|
|
8
92
|
}
|
|
9
|
-
}
|
|
93
|
+
}
|
package/package.json
CHANGED
package/src/accounts.ts
CHANGED
|
@@ -70,6 +70,7 @@ function mergeInfoflowAccountConfig(
|
|
|
70
70
|
name?: string;
|
|
71
71
|
robotName?: string;
|
|
72
72
|
requireMention?: boolean;
|
|
73
|
+
watchMentions?: string[];
|
|
73
74
|
} {
|
|
74
75
|
const raw = getChannelSection(cfg) ?? {};
|
|
75
76
|
const { accounts: _ignored, defaultAccount: _ignored2, ...base } = raw;
|
|
@@ -84,6 +85,7 @@ function mergeInfoflowAccountConfig(
|
|
|
84
85
|
name?: string;
|
|
85
86
|
robotName?: string;
|
|
86
87
|
requireMention?: boolean;
|
|
88
|
+
watchMentions?: string[];
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -126,6 +128,7 @@ export function resolveInfoflowAccount(params: {
|
|
|
126
128
|
appSecret,
|
|
127
129
|
robotName: merged.robotName?.trim() || undefined,
|
|
128
130
|
requireMention: merged.requireMention,
|
|
131
|
+
watchMentions: merged.watchMentions,
|
|
129
132
|
},
|
|
130
133
|
};
|
|
131
134
|
}
|
package/src/actions.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infoflow channel message actions adapter.
|
|
3
|
+
* Intercepts the "send" action from the message tool to support
|
|
4
|
+
* @all and @user mentions in group messages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ChannelMessageActionAdapter, ChannelMessageActionName } from "openclaw/plugin-sdk";
|
|
8
|
+
import { extractToolSend, jsonResult, readStringParam } from "openclaw/plugin-sdk";
|
|
9
|
+
import { resolveInfoflowAccount } from "./accounts.js";
|
|
10
|
+
import { logVerbose } from "./logging.js";
|
|
11
|
+
import { sendInfoflowMessage } from "./send.js";
|
|
12
|
+
import { normalizeInfoflowTarget } from "./targets.js";
|
|
13
|
+
import type { InfoflowMessageContentItem } from "./types.js";
|
|
14
|
+
|
|
15
|
+
export const infoflowMessageActions: ChannelMessageActionAdapter = {
|
|
16
|
+
listActions: (): ChannelMessageActionName[] => ["send"],
|
|
17
|
+
|
|
18
|
+
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
|
|
19
|
+
|
|
20
|
+
handleAction: async ({ action, params, cfg, accountId }) => {
|
|
21
|
+
if (action !== "send") {
|
|
22
|
+
throw new Error(`Action "${action}" is not supported for Infoflow.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const account = resolveInfoflowAccount({ cfg, accountId: accountId ?? undefined });
|
|
26
|
+
if (!account.config.appKey || !account.config.appSecret) {
|
|
27
|
+
throw new Error("Infoflow appKey/appSecret not configured.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rawTo = readStringParam(params, "to", { required: true });
|
|
31
|
+
if (!rawTo) {
|
|
32
|
+
throw new Error("send requires a target (to).");
|
|
33
|
+
}
|
|
34
|
+
const to = normalizeInfoflowTarget(rawTo) ?? rawTo;
|
|
35
|
+
const message = readStringParam(params, "message", { required: false, allowEmpty: true }) ?? "";
|
|
36
|
+
const mediaUrl = readStringParam(params, "media", { trim: false });
|
|
37
|
+
|
|
38
|
+
// Infoflow-specific mention params
|
|
39
|
+
const atAll = params.atAll === true || params.atAll === "true";
|
|
40
|
+
const mentionUserIdsRaw = readStringParam(params, "mentionUserIds");
|
|
41
|
+
|
|
42
|
+
const isGroup = /^group:\d+$/i.test(to);
|
|
43
|
+
const contents: InfoflowMessageContentItem[] = [];
|
|
44
|
+
|
|
45
|
+
// Build AT content nodes (group messages only)
|
|
46
|
+
if (isGroup) {
|
|
47
|
+
if (atAll) {
|
|
48
|
+
contents.push({ type: "at", content: "all" });
|
|
49
|
+
} else if (mentionUserIdsRaw) {
|
|
50
|
+
const userIds = mentionUserIdsRaw
|
|
51
|
+
.split(",")
|
|
52
|
+
.map((s) => s.trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
if (userIds.length > 0) {
|
|
55
|
+
contents.push({ type: "at", content: userIds.join(",") });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prepend @all/@user prefix to display text (same pattern as reply-dispatcher.ts)
|
|
61
|
+
let messageText = message;
|
|
62
|
+
if (isGroup) {
|
|
63
|
+
if (atAll) {
|
|
64
|
+
messageText = `@all ${message}`;
|
|
65
|
+
} else if (mentionUserIdsRaw) {
|
|
66
|
+
const userIds = mentionUserIdsRaw
|
|
67
|
+
.split(",")
|
|
68
|
+
.map((s) => s.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
if (userIds.length > 0) {
|
|
71
|
+
const prefix = userIds.map((id) => `@${id}`).join(" ");
|
|
72
|
+
messageText = `${prefix} ${message}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (messageText.trim()) {
|
|
78
|
+
contents.push({ type: "markdown", content: messageText });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (mediaUrl) {
|
|
82
|
+
contents.push({ type: "link", content: mediaUrl });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (contents.length === 0) {
|
|
86
|
+
throw new Error("send requires text or media");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
logVerbose(
|
|
90
|
+
`[infoflow:action:send] to=${to}, atAll=${atAll}, mentionUserIds=${mentionUserIdsRaw ?? "none"}`,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const result = await sendInfoflowMessage({
|
|
94
|
+
cfg,
|
|
95
|
+
to,
|
|
96
|
+
contents,
|
|
97
|
+
accountId: accountId ?? undefined,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return jsonResult({
|
|
101
|
+
ok: result.ok,
|
|
102
|
+
channel: "infoflow",
|
|
103
|
+
to,
|
|
104
|
+
messageId: result.messageId ?? (result.ok ? "sent" : "failed"),
|
|
105
|
+
...(result.error ? { error: result.error } : {}),
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
};
|