@chbo297/infoflow 2026.5.9-beta.1 → 2026.5.9-beta.2
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 +132 -35
- package/dist/src/actions.js +61 -4
- package/dist/src/bot.js +10 -11
- package/dist/src/recall-intent.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,35 +39,49 @@ BAIDU_NPM_REGISTRY=http://registry.npm.baidu-int.com bash scripts/deploy.sh
|
|
|
39
39
|
|
|
40
40
|
### 首次安装(推荐命令)
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
下面的安装命令块由 `npm run sync-readme-install-version` 自动维护,版本号始终与 npm 上当前的 `latest` / `beta` dist-tag 保持一致,请直接复制使用。
|
|
43
43
|
|
|
44
|
-
方式 A:通过独立 tools 包安装并部署(推荐,支持 `update` 子命令)
|
|
44
|
+
#### 方式 A:通过独立 tools 包安装并部署(推荐,支持 `update` 子命令)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
正式版(`latest` dist-tag):
|
|
47
|
+
|
|
48
|
+
<!-- sync:infoflow-plugin-version:latest -->
|
|
47
49
|
```bash
|
|
48
|
-
|
|
49
|
-
npx -y @chbo297/infoflow-openclaw-tools update
|
|
50
|
+
npm cache clean --force
|
|
51
|
+
npx -y --prefer-online @chbo297/infoflow-openclaw-tools update \
|
|
52
|
+
--version 2026.5.8 --registry https://registry.npmjs.org
|
|
50
53
|
```
|
|
51
|
-
<!-- /sync:infoflow-plugin-version -->
|
|
54
|
+
<!-- /sync:infoflow-plugin-version:latest -->
|
|
55
|
+
|
|
56
|
+
Beta 版(`beta` dist-tag,按需):
|
|
52
57
|
|
|
58
|
+
<!-- sync:infoflow-plugin-version:beta -->
|
|
53
59
|
```bash
|
|
54
|
-
|
|
55
|
-
npx -y @chbo297/infoflow-openclaw-tools@beta update
|
|
60
|
+
npm cache clean --force
|
|
61
|
+
npx -y --prefer-online @chbo297/infoflow-openclaw-tools@beta update \
|
|
62
|
+
--version 2026.5.9-beta.1 --registry https://registry.npmjs.org
|
|
56
63
|
```
|
|
64
|
+
<!-- /sync:infoflow-plugin-version:beta -->
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
> 加上 `npm cache clean --force` 和 `--prefer-online`,可避免本机 npm metadata 缓存尚未刷新而看不到刚发布版本(典型表现为 `ETARGET: No matching version found`)。
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
#### 方式 B:通过 OpenClaw 插件命令安装
|
|
69
|
+
|
|
70
|
+
正式版:
|
|
71
|
+
|
|
72
|
+
<!-- sync:infoflow-plugin-version:latest -->
|
|
61
73
|
```bash
|
|
62
|
-
|
|
63
|
-
openclaw plugins install @chbo297/infoflow@2026.5.9-beta.1
|
|
74
|
+
openclaw plugins install @chbo297/infoflow@2026.5.8
|
|
64
75
|
```
|
|
65
|
-
<!-- /sync:infoflow-plugin-version -->
|
|
76
|
+
<!-- /sync:infoflow-plugin-version:latest -->
|
|
77
|
+
|
|
78
|
+
Beta 版:
|
|
66
79
|
|
|
80
|
+
<!-- sync:infoflow-plugin-version:beta -->
|
|
67
81
|
```bash
|
|
68
|
-
|
|
69
|
-
openclaw plugins install @chbo297/infoflow@2026.5.8-beta.1
|
|
82
|
+
openclaw plugins install @chbo297/infoflow@2026.5.9-beta.1
|
|
70
83
|
```
|
|
84
|
+
<!-- /sync:infoflow-plugin-version:beta -->
|
|
71
85
|
|
|
72
86
|
安装后建议检查插件状态:
|
|
73
87
|
|
|
@@ -76,17 +90,32 @@ openclaw plugins list
|
|
|
76
90
|
openclaw plugins inspect infoflow
|
|
77
91
|
```
|
|
78
92
|
|
|
79
|
-
|
|
93
|
+
#### 如遇 `ETARGET: No matching version found`
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
刚发布的版本可能在本机 npm metadata 缓存里看不到,按下面顺序排查:
|
|
82
96
|
|
|
83
|
-
<!-- sync:infoflow-plugin-version -->
|
|
84
97
|
```bash
|
|
85
|
-
|
|
98
|
+
# 1) 强制清缓存 + 在线拉取最新元数据
|
|
99
|
+
npm cache clean --force
|
|
100
|
+
npx -y --prefer-online @chbo297/infoflow-openclaw-tools@beta update \
|
|
101
|
+
--version <要装的版本> --registry https://registry.npmjs.org
|
|
102
|
+
|
|
103
|
+
# 2) 直接查 registry,确认版本确实可见
|
|
104
|
+
npm view @chbo297/infoflow versions --registry https://registry.npmjs.org
|
|
105
|
+
|
|
106
|
+
# 3) 确认默认 registry 未被改到镜像源(有些内网会重写到 cnpm/baidu 镜像,那里同步可能滞后)
|
|
107
|
+
npm config get registry # 期望: https://registry.npmjs.org/
|
|
108
|
+
# 临时强制覆盖(不改全局配置):
|
|
109
|
+
npm_config_registry=https://registry.npmjs.org \
|
|
110
|
+
npx -y --prefer-online @chbo297/infoflow-openclaw-tools@beta update \
|
|
111
|
+
--version <要装的版本> --registry https://registry.npmjs.org
|
|
112
|
+
|
|
113
|
+
# 4) 直接 curl 验证那台机器能否拿到 manifest
|
|
114
|
+
curl -sI https://registry.npmjs.org/@chbo297/infoflow | head -5
|
|
115
|
+
curl -s https://registry.npmjs.org/@chbo297/infoflow/<要装的版本> | head -50
|
|
86
116
|
```
|
|
87
|
-
<!-- /sync:infoflow-plugin-version -->
|
|
88
117
|
|
|
89
|
-
|
|
118
|
+
### tools 包的常用参数
|
|
90
119
|
|
|
91
120
|
- `--version <version>`: 指定安装版本(默认 `latest`)
|
|
92
121
|
- `--registry <url>`: 插件包下载源(默认 `https://registry.npmjs.org`)
|
|
@@ -107,34 +136,102 @@ npx -y @chbo297/infoflow-openclaw-tools update --version 2026.5.9-beta.1 --regis
|
|
|
107
136
|
|
|
108
137
|
### 版本升级、打 tag、推送与 npm 发布流程
|
|
109
138
|
|
|
110
|
-
|
|
139
|
+
正式版与 Beta 预发各自有完整流程,区别只在 **`npm version` 用的版本号** 和 **`npm publish` 是否带 `--tag beta`** 两处。请按需选择对应小节,从头到尾执行。
|
|
140
|
+
|
|
141
|
+
执行 `npm run sync-readme-install-version` 时脚本会:
|
|
142
|
+
|
|
143
|
+
- 把"发版流程"代码块内 `--version` / `npm version` / `git tag` / `git commit -m "<version>"` 等示例同步到当前 `package.json.version`("current" stream);
|
|
144
|
+
- 同时把上文"首次安装"段落里 `:latest` / `:beta` 两个 marker 区按 npm 上的 dist-tag 刷新——发 stable 时 `:latest` 区会写成新版本号;发 prerelease 时 `:beta` 区会写成新版本号;另一条 stream 通过 npm registry 接口拉取真实 dist-tag。
|
|
145
|
+
|
|
146
|
+
#### A. 正式版(stable)发布流程
|
|
147
|
+
|
|
148
|
+
发布一个不带预发后缀的版本(例如 `2026.5.10`),会同时占用 npm 的 `latest` dist-tag,是 `npx` 默认拉取的版本。
|
|
149
|
+
|
|
150
|
+
将下方所有 `<X.Y.Z>` 替换为目标正式版本号(不带 `-beta.N`):
|
|
111
151
|
|
|
112
|
-
<!-- sync:infoflow-plugin-version -->
|
|
113
152
|
```bash
|
|
114
153
|
# 1) 修改版本号(会同步 package-lock.json)
|
|
115
|
-
npm version
|
|
154
|
+
npm version <X.Y.Z> --no-git-tag-version
|
|
116
155
|
|
|
117
|
-
# 2) 同步 README
|
|
156
|
+
# 2) 同步 README:current 区写入 <X.Y.Z>;:latest 区也写入 <X.Y.Z>(因为本次发的就是新 latest);
|
|
157
|
+
# :beta 区从 npm 拉取当前 beta dist-tag 保持不变
|
|
118
158
|
npm run sync-readme-install-version
|
|
119
159
|
|
|
120
|
-
# 3)
|
|
160
|
+
# 3) 编辑 CHANGELOG.md 顶部,添加本版本章节
|
|
161
|
+
|
|
162
|
+
# 4) 发布前校验
|
|
121
163
|
npm run typecheck
|
|
122
164
|
npm run test
|
|
123
165
|
npm run build
|
|
124
166
|
|
|
125
|
-
#
|
|
167
|
+
# 5) 提交版本变更
|
|
126
168
|
git add package.json package-lock.json README.md CHANGELOG.md scripts src
|
|
127
|
-
git commit -m "
|
|
169
|
+
git commit -m "<X.Y.Z>"
|
|
128
170
|
|
|
129
|
-
#
|
|
130
|
-
git tag
|
|
171
|
+
# 6) 打 tag 并推送代码与 tag
|
|
172
|
+
git tag <X.Y.Z>
|
|
131
173
|
git push origin main
|
|
132
|
-
git push origin
|
|
174
|
+
git push origin <X.Y.Z>
|
|
175
|
+
|
|
176
|
+
# 7) 发布到 npm(占用 latest dist-tag)
|
|
177
|
+
npm publish --registry https://registry.npmjs.org
|
|
178
|
+
|
|
179
|
+
# 8) 发布成功后再跑一次 sync,把 :latest 区刷新成 npm registry 真实状态(通常已经一致,
|
|
180
|
+
# 但若另一条 stream 在期间也有新发布,这一步会顺带更新),并补一个 docs 提交
|
|
181
|
+
npm run sync-readme-install-version
|
|
182
|
+
git add README.md && git diff --cached --quiet || git commit -m "docs: refresh README install commands"
|
|
183
|
+
git push origin main
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### B. Beta 预发布流程
|
|
133
187
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
188
|
+
发布一个带预发后缀的版本(例如 `2026.5.10-beta.1`),通过 `--tag beta` 占用 `beta` dist-tag,**不会**改写 `latest`,默认 `npx` 装到的仍是正式版。
|
|
189
|
+
|
|
190
|
+
将下方所有 `<X.Y.Z-beta.N>` 替换为目标预发版本号:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# 1) 修改版本号(会同步 package-lock.json)
|
|
194
|
+
npm version <X.Y.Z-beta.N> --no-git-tag-version
|
|
195
|
+
|
|
196
|
+
# 2) 同步 README:current 区写入 <X.Y.Z-beta.N>;:beta 区也写入 <X.Y.Z-beta.N>(因为本次发的就是新 beta);
|
|
197
|
+
# :latest 区从 npm 拉取当前 latest dist-tag 保持不变
|
|
198
|
+
npm run sync-readme-install-version
|
|
199
|
+
|
|
200
|
+
# 3) 编辑 CHANGELOG.md 顶部,添加本版本章节
|
|
201
|
+
|
|
202
|
+
# 4) 发布前校验
|
|
203
|
+
npm run typecheck
|
|
204
|
+
npm run test
|
|
205
|
+
npm run build
|
|
206
|
+
|
|
207
|
+
# 5) 提交版本变更
|
|
208
|
+
git add package.json package-lock.json README.md CHANGELOG.md scripts src
|
|
209
|
+
git commit -m "<X.Y.Z-beta.N>"
|
|
210
|
+
|
|
211
|
+
# 6) 打 tag 并推送代码与 tag
|
|
212
|
+
git tag <X.Y.Z-beta.N>
|
|
213
|
+
git push origin main
|
|
214
|
+
git push origin <X.Y.Z-beta.N>
|
|
215
|
+
|
|
216
|
+
# 7) 发布到 npm(占用 beta dist-tag;不影响 latest)
|
|
217
|
+
npm publish --tag beta --registry https://registry.npmjs.org
|
|
218
|
+
|
|
219
|
+
# 8) 发布成功后再跑一次 sync 并补一个 docs 提交(同 A.8)
|
|
220
|
+
npm run sync-readme-install-version
|
|
221
|
+
git add README.md && git diff --cached --quiet || git commit -m "docs: refresh README install commands"
|
|
222
|
+
git push origin main
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### 当前 `package.json` 的版本号示例
|
|
226
|
+
|
|
227
|
+
以下示例由 `sync` 脚本自动维护,反映**仓库当前 `package.json.version`**(可作为复制 A / B 流程时的版本号参考):
|
|
228
|
+
|
|
229
|
+
<!-- sync:infoflow-plugin-version -->
|
|
230
|
+
```bash
|
|
231
|
+
npm version 2026.5.9-beta.1 --no-git-tag-version
|
|
232
|
+
git commit -m "2026.5.9-beta.1"
|
|
233
|
+
git tag 2026.5.9-beta.1
|
|
234
|
+
git push origin 2026.5.9-beta.1
|
|
138
235
|
```
|
|
139
236
|
<!-- /sync:infoflow-plugin-version -->
|
|
140
237
|
|
package/dist/src/actions.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
7
7
|
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
|
8
8
|
import { resolveInfoflowAccount } from "./accounts.js";
|
|
9
|
+
import { looksLikeRecallLatest } from "./recall-intent.js";
|
|
9
10
|
import { lookupInboundContext } from "./inbound-context.js";
|
|
10
11
|
import { logVerbose } from "./logging.js";
|
|
11
12
|
import { prepareInfoflowImageBase64, sendInfoflowImageMessage } from "./media.js";
|
|
@@ -34,7 +35,44 @@ function resolveInboundReplyToMessageId(params) {
|
|
|
34
35
|
return undefined;
|
|
35
36
|
if (ctx.target !== params.target)
|
|
36
37
|
return undefined;
|
|
37
|
-
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Aggressive guard: when LLM passes messageId === inbound currentMessageId (a known
|
|
41
|
+
* confusion pattern), auto-correct based on context instead of failing.
|
|
42
|
+
*
|
|
43
|
+
* Priorities:
|
|
44
|
+
* 1) Use replyToMessageId — user quote-replied to a bot message (highest confidence).
|
|
45
|
+
* 2) Drop to count=1 mode — when no replyTo AND text indicates "recall latest one".
|
|
46
|
+
* 3) Defer to existing fallback chain — ambiguous intent (e.g., "recall the one about X").
|
|
47
|
+
*
|
|
48
|
+
* Returns the corrected messageId (or undefined to signal count=1 mode).
|
|
49
|
+
*/
|
|
50
|
+
function applyAggressiveGuardForInboundMessageId(params) {
|
|
51
|
+
const inboundMsgId = params.currentMessageId != null ? String(params.currentMessageId) : undefined;
|
|
52
|
+
// Guard only triggers when LLM passes the inbound message id as delete target
|
|
53
|
+
if (!inboundMsgId || params.messageId !== inboundMsgId) {
|
|
54
|
+
return params.messageId;
|
|
55
|
+
}
|
|
56
|
+
const ctxRec = lookupInboundContext(inboundMsgId);
|
|
57
|
+
const scopeOk = ctxRec && ctxRec.accountId === params.accountId && ctxRec.target === params.target;
|
|
58
|
+
if (!scopeOk) {
|
|
59
|
+
// No inbound context to guide correction — defer to existing fallback chain
|
|
60
|
+
return params.messageId;
|
|
61
|
+
}
|
|
62
|
+
// Priority 1: replyToMessageId (user quote-replied to a bot message)
|
|
63
|
+
const replyToId = ctxRec.replyToMessageId;
|
|
64
|
+
if (replyToId && findSentMessage(params.accountId, replyToId)) {
|
|
65
|
+
logVerbose(`[infoflow:delete] aggressive: messageId==inboundMsgId(${params.messageId}); using replyTo=${replyToId}`);
|
|
66
|
+
return replyToId;
|
|
67
|
+
}
|
|
68
|
+
// Priority 2: text indicates "recall latest one" — safe to auto-correct to count=1
|
|
69
|
+
if (looksLikeRecallLatest(ctxRec.inboundBody ?? "")) {
|
|
70
|
+
logVerbose(`[infoflow:delete] aggressive: messageId==inboundMsgId(${params.messageId}); recall-latest intent → drop to count=1`);
|
|
71
|
+
return undefined; // undefined → count=1 mode
|
|
72
|
+
}
|
|
73
|
+
// Priority 3: ambiguous intent — defer to existing fallback chain
|
|
74
|
+
logVerbose(`[infoflow:delete] aggressive: messageId==inboundMsgId(${params.messageId}); ambiguous intent → defer to candidate-error path`);
|
|
75
|
+
return params.messageId;
|
|
38
76
|
}
|
|
39
77
|
/** Format up to N recent sent messages for an error-path hint to the LLM. */
|
|
40
78
|
function formatRecentCandidatesForError(records, limit = 5) {
|
|
@@ -76,7 +114,7 @@ export const infoflowMessageActions = {
|
|
|
76
114
|
if (!account.config.appKey || !account.config.appSecret) {
|
|
77
115
|
throw new Error("Infoflow appKey/appSecret not configured.");
|
|
78
116
|
}
|
|
79
|
-
|
|
117
|
+
let messageId = readStringParam(params, "messageId");
|
|
80
118
|
// Default to count=1 (recall latest message) when neither messageId nor count is provided
|
|
81
119
|
const countStr = readStringParam(params, "count") ?? (messageId ? undefined : "1");
|
|
82
120
|
const groupMatch = target.match(/^group:(\d+)/i);
|
|
@@ -85,6 +123,16 @@ export const infoflowMessageActions = {
|
|
|
85
123
|
// 群消息撤回
|
|
86
124
|
// -----------------------------------------------------------------
|
|
87
125
|
const groupId = Number(groupMatch[1]);
|
|
126
|
+
const targetForStore = `group:${groupId}`;
|
|
127
|
+
// Apply aggressive guard when messageId equals inbound currentMessageId (LLM confusion pattern)
|
|
128
|
+
if (messageId) {
|
|
129
|
+
messageId = applyAggressiveGuardForInboundMessageId({
|
|
130
|
+
messageId,
|
|
131
|
+
currentMessageId: toolContext?.currentMessageId,
|
|
132
|
+
accountId: account.accountId,
|
|
133
|
+
target: targetForStore,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
88
136
|
// Mode A: single message recall by messageId
|
|
89
137
|
if (messageId) {
|
|
90
138
|
// Resolve msgseqid (group recall requires it). If the LLM-passed messageId
|
|
@@ -97,7 +145,7 @@ export const infoflowMessageActions = {
|
|
|
97
145
|
if (!stored && !msgseqid) {
|
|
98
146
|
const fallbackId = resolveInboundReplyToMessageId({
|
|
99
147
|
accountId: account.accountId,
|
|
100
|
-
target:
|
|
148
|
+
target: targetForStore,
|
|
101
149
|
currentMessageId: toolContext?.currentMessageId,
|
|
102
150
|
});
|
|
103
151
|
if (fallbackId && fallbackId !== effectiveMessageId) {
|
|
@@ -151,7 +199,7 @@ export const infoflowMessageActions = {
|
|
|
151
199
|
throw new Error("count must be a positive integer.");
|
|
152
200
|
}
|
|
153
201
|
const records = querySentMessages(account.accountId, {
|
|
154
|
-
target:
|
|
202
|
+
target: targetForStore,
|
|
155
203
|
count,
|
|
156
204
|
});
|
|
157
205
|
// Filter to records that have msgseqid (required for group recall)
|
|
@@ -221,6 +269,15 @@ export const infoflowMessageActions = {
|
|
|
221
269
|
throw new Error("Infoflow private message recall requires appAgentId configuration. " +
|
|
222
270
|
"Set channels.infoflow.appAgentId to your application ID (如流企业后台的应用ID).");
|
|
223
271
|
}
|
|
272
|
+
// Apply aggressive guard when messageId equals inbound currentMessageId (LLM confusion pattern)
|
|
273
|
+
if (messageId) {
|
|
274
|
+
messageId = applyAggressiveGuardForInboundMessageId({
|
|
275
|
+
messageId,
|
|
276
|
+
currentMessageId: toolContext?.currentMessageId,
|
|
277
|
+
accountId: account.accountId,
|
|
278
|
+
target,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
224
281
|
// Mode A: single message recall by messageId (msgkey)
|
|
225
282
|
if (messageId) {
|
|
226
283
|
// Attempt the inbound-context fallback when the LLM-passed messageId is
|
package/dist/src/bot.js
CHANGED
|
@@ -4,6 +4,7 @@ import { resolveInfoflowAccount } from "./accounts.js";
|
|
|
4
4
|
import { registerInboundContext } from "./inbound-context.js";
|
|
5
5
|
import { getInfoflowBotLog, formatInfoflowError, logVerbose } from "./logging.js";
|
|
6
6
|
import { createInfoflowReplyDispatcher } from "./reply-dispatcher.js";
|
|
7
|
+
import { looksLikeRecallIntent, looksLikeRecallLatest } from "./recall-intent.js";
|
|
7
8
|
import { getInfoflowRuntime } from "./runtime.js";
|
|
8
9
|
import { findSentMessage, querySentMessages } from "./sent-message-store.js";
|
|
9
10
|
/**
|
|
@@ -226,13 +227,7 @@ function resolveReplyTargets(bodyItems, accountId) {
|
|
|
226
227
|
// in the target group" bug. sent-messages.db tracks all bot-sent messages
|
|
227
228
|
// keyed by target, so a single push surfaces messages sent from any session.
|
|
228
229
|
// ---------------------------------------------------------------------------
|
|
229
|
-
|
|
230
|
-
const RECALL_INTENT_REGEX = /(撤回|收回|删[掉了除]|取消|清除|recall|unsend|undo\s*send|delete\s+(?:that|those|the\s+(?:last|previous(?:\s+\d+)?)))/i;
|
|
231
|
-
function looksLikeRecallIntent(text) {
|
|
232
|
-
if (!text)
|
|
233
|
-
return false;
|
|
234
|
-
return RECALL_INTENT_REGEX.test(text);
|
|
235
|
-
}
|
|
230
|
+
// Recall-intent detection lives in ./recall-intent.js — shared with actions.ts.
|
|
236
231
|
const RECENT_BOT_AMBIENT_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
237
232
|
const RECENT_BOT_AMBIENT_COUNT = 5;
|
|
238
233
|
const RECENT_BOT_DETAIL_COUNT = 10;
|
|
@@ -886,10 +881,11 @@ export async function handleInfoflowMessage(params) {
|
|
|
886
881
|
}
|
|
887
882
|
}
|
|
888
883
|
// Register inbound context so the delete action handler can fall back to the
|
|
889
|
-
// bot-message id the inbound is quote-replying to (when present)
|
|
890
|
-
//
|
|
891
|
-
//
|
|
892
|
-
//
|
|
884
|
+
// bot-message id the inbound is quote-replying to (when present), or detect
|
|
885
|
+
// the "messageId===inboundMessageId" LLM confusion pattern with the body
|
|
886
|
+
// text to decide whether it's safe to auto-correct to count=1. We only pick
|
|
887
|
+
// a bot-sent reply target: falling back to a non-bot reply id would never
|
|
888
|
+
// help (it can't be in sent-messages.db) and only adds noise.
|
|
893
889
|
if (event.messageId) {
|
|
894
890
|
registerInboundContext({
|
|
895
891
|
accountId,
|
|
@@ -897,6 +893,7 @@ export async function handleInfoflowMessage(params) {
|
|
|
897
893
|
inboundMessageId: event.messageId,
|
|
898
894
|
replyToMessageId: event.replyTargets?.find((t) => t.isBotMessage)?.messageid,
|
|
899
895
|
replyTargets: event.replyTargets,
|
|
896
|
+
inboundBody: bodyForAgent || mes || event.replyContext?.join(" "),
|
|
900
897
|
registeredAt: Date.now(),
|
|
901
898
|
});
|
|
902
899
|
}
|
|
@@ -1163,6 +1160,8 @@ export function _checkReplyToBot(bodyItems, accountId) {
|
|
|
1163
1160
|
export const _buildGroupOutputHygienePrompt = buildGroupOutputHygienePrompt;
|
|
1164
1161
|
/** @internal — Recall intent regex. Only exported for tests. */
|
|
1165
1162
|
export const _looksLikeRecallIntent = looksLikeRecallIntent;
|
|
1163
|
+
/** @internal — Stricter recall-latest detector. Only exported for tests. */
|
|
1164
|
+
export const _looksLikeRecallLatest = looksLikeRecallLatest;
|
|
1166
1165
|
/** @internal — Sent-messages section builder. Only exported for tests. */
|
|
1167
1166
|
export const _buildBotRecentMessagesSection = buildBotRecentMessagesSection;
|
|
1168
1167
|
/** @internal — Quoted-reply targets section builder. Only exported for tests. */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight recall-intent detection used by both the prompt-injection path
|
|
3
|
+
* (bot.ts) and the delete action's aggressive guard (actions.ts).
|
|
4
|
+
*
|
|
5
|
+
* Two helpers:
|
|
6
|
+
* - looksLikeRecallIntent: matches any "撤回/删除/recall/unsend/..." verb.
|
|
7
|
+
* - looksLikeRecallLatest: requires both a recall verb AND an explicit
|
|
8
|
+
* "the latest one" qualifier (上一条 / 最后一条 / 刚才那条 / 最近一条 /
|
|
9
|
+
* last / previous / most recent / ...). Used to decide whether it's safe
|
|
10
|
+
* to auto-correct `messageId=inbound_user_msg_id` to count=1 (recall most
|
|
11
|
+
* recent). Standalone "撤回那条" without a temporal qualifier is rejected:
|
|
12
|
+
* it could refer to a specific quoted message and we'd rather surface
|
|
13
|
+
* candidates to the LLM than risk recalling the wrong one.
|
|
14
|
+
*/
|
|
15
|
+
const RECALL_INTENT_REGEX = /(撤回|收回|删[掉了除]|取消|清除|recall|unsend|undo\s*send|delete\s+(?:that|those|the\s+(?:last|previous(?:\s+\d+)?)))/i;
|
|
16
|
+
const RECALL_LATEST_HINT_REGEX = /(上一?条|最后一?条|刚才那?条|最近一?条|last(?:\s+(?:one|message|two|few|reply))?|previous|most\s*recent)/iu;
|
|
17
|
+
export function looksLikeRecallIntent(text) {
|
|
18
|
+
if (!text)
|
|
19
|
+
return false;
|
|
20
|
+
return RECALL_INTENT_REGEX.test(text);
|
|
21
|
+
}
|
|
22
|
+
export function looksLikeRecallLatest(text) {
|
|
23
|
+
if (!text)
|
|
24
|
+
return false;
|
|
25
|
+
return RECALL_INTENT_REGEX.test(text) && RECALL_LATEST_HINT_REGEX.test(text);
|
|
26
|
+
}
|