koishi-plugin-chatluna-think-viewer 1.0.19 → 2.0.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.
- package/README.md +47 -43
- package/index.js +53 -33
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -1,44 +1,48 @@
|
|
|
1
|
-
# koishi-plugin-chatluna-think-viewer
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
chatluna-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
allowPrivate: false
|
|
26
|
-
emptyMessage:
|
|
27
|
-
#
|
|
28
|
-
guardEnabled: true
|
|
29
|
-
guardMode: recall # recall | block
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
##
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
##
|
|
1
|
+
# koishi-plugin-chatluna-think-viewer
|
|
2
|
+
|
|
3
|
+
通过命令/关键词查看 `chatluna-character` 最近一次回复的 `<think>` 思考内容,并提供“异常格式自动撤回/拦截”守卫。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
- 依赖 `chatluna-character` 存储的思考上下文,支持命令与前缀关键词调用。
|
|
7
|
+
- 支持群聊使用(可配置是否允许私聊)。
|
|
8
|
+
- **异常格式自动撤回/拦截**:默认检测 `<think>`、`<status>`、`<output>`、`<analysis>`、`<system>` 等块或调试 JSON、think/json/yaml 代码块;命中后可选择先发后撤回(recall)或直接阻止(block)。
|
|
9
|
+
- **严格输出模式(小白化)**:可选仅允许 `<output><message>…</message></output>` 结构;@ 仅允许数字 user_id,1~5 条 message,不符合即拦截/撤回。
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
```bash
|
|
13
|
+
# Koishi 控制台市场搜索 chatluna-think-viewer 安装
|
|
14
|
+
npm install koishi-plugin-chatluna-think-viewer
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 配置示例 (koishi.yml)
|
|
18
|
+
```yaml
|
|
19
|
+
plugins:
|
|
20
|
+
chatluna-character: {}
|
|
21
|
+
chatluna-think-viewer:
|
|
22
|
+
command: think
|
|
23
|
+
keywords:
|
|
24
|
+
- 查看思考
|
|
25
|
+
allowPrivate: false
|
|
26
|
+
emptyMessage: 暂时没有可用的思考记录。
|
|
27
|
+
# 异常撤回/拦截
|
|
28
|
+
guardEnabled: true
|
|
29
|
+
guardMode: recall # recall | block
|
|
30
|
+
guardDelay: 1 # 撤回延迟(秒),block 模式忽略
|
|
31
|
+
guardStrictOutputOnly: true # 只允许 <output><message>…</message></output>
|
|
32
|
+
guardStrictPattern: '^\s*<output>\s*(<message>(?:<at>\d+<\/at>\s*)?(?:<sticker>[^<]*<\/sticker>|[^<]*)<\/message>\s*){1,5}<\/output>\s*$'
|
|
33
|
+
guardForbiddenPatterns:
|
|
34
|
+
- '<think>[\\s\\S]*?<\\/think>'
|
|
35
|
+
- '<status>[\\s\\S]*?<\\/status>'
|
|
36
|
+
- '```\\s*think[\\s\\S]*?```'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 使用
|
|
40
|
+
- 群聊里发送 `think` 或配置的关键词查看最近一次 `<think>` 内容;`think 2` 查看倒数第 2 条。
|
|
41
|
+
- 异常/严格模式:当 bot 发送的消息命中禁用规则或不符合严格输出结构时,记录日志并撤回(或直接阻止发送)。
|
|
42
|
+
|
|
43
|
+
## 依赖
|
|
44
|
+
- koishi >= 4.18.0
|
|
45
|
+
- koishi-plugin-chatluna-character >= 0.0.180
|
|
46
|
+
|
|
47
|
+
## 协议
|
|
44
48
|
MIT
|
package/index.js
CHANGED
|
@@ -10,35 +10,53 @@ const inject = {
|
|
|
10
10
|
|
|
11
11
|
const defaultForbidden = [
|
|
12
12
|
'<think>[\\s\\S]*?<\\/think>',
|
|
13
|
+
'<status>[\\s\\S]*?<\\/status>',
|
|
14
|
+
'<output>[\\s\\S]*?<\\/output>',
|
|
15
|
+
'<analysis>[\\s\\S]*?<\\/analysis>',
|
|
16
|
+
'<system>[\\s\\S]*?<\\/system>',
|
|
13
17
|
'```\\s*think[\\s\\S]*?```',
|
|
18
|
+
'```\\s*(json|yaml|yml)[\\s\\S]*?```',
|
|
14
19
|
'"role"\\s*:\\s*"assistant"',
|
|
15
20
|
'"analysis"\\s*:',
|
|
16
21
|
'"thought"\\s*:',
|
|
22
|
+
'(?:human_relations|人际关系)\\s*[:=]',
|
|
23
|
+
'(?:memory|记忆|记忆点|总结)\\s*[:=]',
|
|
17
24
|
];
|
|
18
25
|
|
|
26
|
+
// 严格 <output><message>... 结构:允许文本 / <at>user_id</at> 文本 / <sticker>url</sticker>
|
|
27
|
+
// 1~5 条 message,@ 仅允许数字 user_id
|
|
28
|
+
const strictOutputPattern =
|
|
29
|
+
'^\\s*<output>\\s*(<message>(?:<at>\\d+<\\/at>\\s*)?(?:<sticker>[^<]*<\\/sticker>|[^<]*)<\\/message>\\s*){1,5}<\\/output>\\s*$';
|
|
30
|
+
|
|
19
31
|
const Config = Schema.intersect([
|
|
20
32
|
Schema.object({
|
|
21
|
-
command: Schema.string().default('think').description('
|
|
22
|
-
keywords: Schema.array(Schema.string()).default(['
|
|
23
|
-
allowPrivate: Schema.boolean().default(false).description('
|
|
24
|
-
emptyMessage: Schema.string().default('
|
|
25
|
-
renderImage: Schema.boolean().default(false).description('
|
|
26
|
-
}).description('
|
|
33
|
+
command: Schema.string().default('think').description('\u67e5\u770b\u601d\u8003\u5185\u5bb9\u7684\u6307\u4ee4\u540d'),
|
|
34
|
+
keywords: Schema.array(Schema.string()).default(['\u67e5\u770b\u601d\u8003', '\u4e0a\u6b21\u601d\u8003']).description('\u53ef\u65e0\u524d\u7f00\u89e6\u53d1\u7684\u5173\u952e\u8bcd'),
|
|
35
|
+
allowPrivate: Schema.boolean().default(false).description('\u662f\u5426\u5141\u8bb8\u5728\u79c1\u804a\u4e2d\u4f7f\u7528'),
|
|
36
|
+
emptyMessage: Schema.string().default('\u6682\u65f6\u6ca1\u6709\u53ef\u7528\u7684\u601d\u8003\u8bb0\u5f55\u3002').description('\u6ca1\u6709\u8bb0\u5f55\u65f6\u7684\u63d0\u793a\u6587\u672c'),
|
|
37
|
+
renderImage: Schema.boolean().default(false).description('\u662f\u5426\u901a\u8fc7\u0020\u0043\u0068\u0061\u0074\u004c\u0075\u006e\u0061\u0020\u0069\u006d\u0061\u0067\u0065\u0020\u0072\u0065\u006e\u0064\u0065\u0072\u0065\u0072\u0020\u5c06\u601d\u8003\u6e32\u67d3\u4e3a\u56fe\u7247\uff0c\u5931\u8d25\u65f6\u56de\u9000\u6587\u672c'),
|
|
38
|
+
}).description('\u601d\u8003\u67e5\u770b\u914d\u7f6e'),
|
|
27
39
|
Schema.object({
|
|
28
|
-
guardEnabled: Schema.boolean().default(true).description('
|
|
29
|
-
guardMode: Schema.union(['recall', 'block']).default('recall').description('
|
|
30
|
-
guardDelay: Schema.number().default(1).min(0).max(60).description('
|
|
31
|
-
guardAllowPrivate: Schema.boolean().default(true).description('
|
|
32
|
-
guardGroups: Schema.array(Schema.string()).default([]).description('
|
|
40
|
+
guardEnabled: Schema.boolean().default(true).description('\u5f02\u5e38\u8f93\u51fa\u81ea\u52a8\u62e6\u622a\u5f00\u5173'),
|
|
41
|
+
guardMode: Schema.union(['recall', 'block']).default('recall').description('\u0072\u0065\u0063\u0061\u006c\u006c\u003d\u5148\u53d1\u9001\u540e\u64a4\u56de\uff0c\u0062\u006c\u006f\u0063\u006b\u003d\u76f4\u63a5\u963b\u6b62\u53d1\u9001'),
|
|
42
|
+
guardDelay: Schema.number().default(1).min(0).max(60).description('\u64a4\u56de\u5ef6\u8fdf\uff08\u79d2\uff09'),
|
|
43
|
+
guardAllowPrivate: Schema.boolean().default(true).description('\u662f\u5426\u5728\u79c1\u804a\u4e2d\u4e5f\u542f\u7528\u62e6\u622a'),
|
|
44
|
+
guardGroups: Schema.array(Schema.string()).default([]).description('\u53ea\u5728\u8fd9\u4e9b\u7fa4\u751f\u6548\uff0c\u7559\u7a7a\u8868\u793a\u5168\u90e8'),
|
|
33
45
|
guardForbiddenPatterns: Schema.array(Schema.string())
|
|
34
46
|
.default(defaultForbidden)
|
|
35
|
-
.description('
|
|
47
|
+
.description('\u547d\u4e2d\u5373\u89c6\u4e3a\u5f02\u5e38\u7684\u6a21\u5f0f\uff0c\u7528\u4e8e\u907f\u514d\u601d\u8003\u6cc4\u9732\u6216\u0020\u004a\u0053\u004f\u004e\u0020\u751f\u51fa'),
|
|
36
48
|
guardAllowedPatterns: Schema.array(Schema.string())
|
|
37
49
|
.default(['[\\s\\S]+'])
|
|
38
|
-
.description('
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
.description('\u53ef\u9009\u767d\u540d\u5355\uff0c\u81f3\u5c11\u5339\u914d\u4e00\u4e2a\u624d\u7b97\u6b63\u5e38'),
|
|
51
|
+
guardStrictOutputOnly: Schema.boolean()
|
|
52
|
+
.default(false)
|
|
53
|
+
.description('\u53ea\u5141\u8bb8\u7b26\u5408 <output><message>\u2026</message></output> \u7ed3\u6784\u7684\u6d88\u606f\uff0c\u4e0d\u7b26\u5408\u5373\u62e6\u622a/\u64a4\u56de'),
|
|
54
|
+
guardStrictPattern: Schema.string()
|
|
55
|
+
.default(strictOutputPattern)
|
|
56
|
+
.description('\u81ea\u5b9a\u4e49\u4e25\u683c\u8f93\u51fa\u6b63\u5219\uff1b\u4e3a\u7a7a\u65f6\u4f7f\u7528\u5185\u7f6e\u7684 <output><message>\u2026</message> \u89c4\u5219'),
|
|
57
|
+
guardLog: Schema.boolean().default(true).description('\u662f\u5426\u5728\u65e5\u5fd7\u8bb0\u5f55\u5f02\u5e38\u539f\u56e0\u548c\u5185\u5bb9'),
|
|
58
|
+
guardContentPreview: Schema.number().default(80).min(10).max(500).description('\u65e5\u5fd7\u5185\u5bb9\u9884\u89c8\u957f\u5ea6'),
|
|
59
|
+
}).description('\u5f02\u5e38\u8f93\u51fa\u81ea\u52a8\u5904\u7406'),
|
|
42
60
|
]);
|
|
43
61
|
|
|
44
62
|
function extractText(content) {
|
|
@@ -60,7 +78,7 @@ function extractText(content) {
|
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
function extractThink(text) {
|
|
63
|
-
//
|
|
81
|
+
// \u67d0\u4e9b\u6a21\u578b/\u4e2d\u95f4\u4ef6\u4f1a\u5728\u540c\u4e00\u6761\u6d88\u606f\u91cc\u591a\u6b21\u51fa\u73b0 <think>\uff0c\u53d6\u6700\u540e\u4e00\u6b21
|
|
64
82
|
let last = '';
|
|
65
83
|
const regex = /<think>([\s\S]*?)<\/think>/gi;
|
|
66
84
|
let m;
|
|
@@ -72,7 +90,7 @@ function extractThink(text) {
|
|
|
72
90
|
|
|
73
91
|
function formatThink(text) {
|
|
74
92
|
if (!text) return text;
|
|
75
|
-
//
|
|
93
|
+
// \u5c1d\u8bd5\u683c\u5f0f\u5316 JSON\uff0c\u5931\u8d25\u5219\u505a\u57fa\u7840\u53bb\u7a7a\u884c/\u7f29\u8fdb\u7f8e\u5316
|
|
76
94
|
try {
|
|
77
95
|
const parsed = JSON.parse(text);
|
|
78
96
|
return JSON.stringify(parsed, null, 2);
|
|
@@ -159,10 +177,10 @@ function compileRegex(list) {
|
|
|
159
177
|
function detectAbnormal(text, forbidden, allowed) {
|
|
160
178
|
if (!text) return null;
|
|
161
179
|
for (const re of forbidden) {
|
|
162
|
-
if (re.test(text)) return
|
|
180
|
+
if (re.test(text)) return `\u547d\u4e2d\u7981\u6b62\u6a21\u5f0f: /${re.source}/`;
|
|
163
181
|
}
|
|
164
182
|
if (allowed.length && !allowed.some((re) => re.test(text))) {
|
|
165
|
-
return '
|
|
183
|
+
return '\u672a\u5339\u914d\u4efb\u4f55\u5141\u8bb8\u6a21\u5f0f';
|
|
166
184
|
}
|
|
167
185
|
return null;
|
|
168
186
|
}
|
|
@@ -187,7 +205,9 @@ function applyGuard(ctx, config) {
|
|
|
187
205
|
if (!config.guardEnabled) return;
|
|
188
206
|
const logger = ctx.logger(`${name}:guard`);
|
|
189
207
|
const forbidden = compileRegex(config.guardForbiddenPatterns);
|
|
190
|
-
const allowed =
|
|
208
|
+
const allowed = config.guardStrictOutputOnly
|
|
209
|
+
? compileRegex([config.guardStrictPattern || strictOutputPattern])
|
|
210
|
+
: compileRegex(config.guardAllowedPatterns);
|
|
191
211
|
const original = Bot.prototype.sendMessage;
|
|
192
212
|
|
|
193
213
|
Bot.prototype.sendMessage = async function patched(channelId, content, referrer, options = {}) {
|
|
@@ -229,10 +249,10 @@ function applyGuard(ctx, config) {
|
|
|
229
249
|
}
|
|
230
250
|
|
|
231
251
|
function apply(ctx, config) {
|
|
232
|
-
//
|
|
252
|
+
// \u601d\u8003\u67e5\u770b\u6307\u4ee4
|
|
233
253
|
const cmd = ctx
|
|
234
|
-
.command(`${config.command} [index:string]`, '
|
|
235
|
-
.usage('
|
|
254
|
+
.command(`${config.command} [index:string]`, '\u8bfb\u53d6\u4e0a\u4e00\u6761\u542b <think> \u7684\u5185\u5bb9\uff0c\u53ef\u6307\u5b9a\u5012\u6570\u7b2c N \u6761')
|
|
255
|
+
.usage('\u4e0d\u5e26\u53c2\u6570\u9ed8\u8ba4\u6700\u65b0\uff1b\u793a\u4f8b\uff1athink 2 \u67e5\u8be2\u5012\u6570\u7b2c 2 \u6761 AI \u56de\u590d\u7684\u601d\u8003');
|
|
236
256
|
|
|
237
257
|
for (const keyword of config.keywords || []) {
|
|
238
258
|
cmd.shortcut(keyword, { prefix: false });
|
|
@@ -240,23 +260,23 @@ function apply(ctx, config) {
|
|
|
240
260
|
|
|
241
261
|
cmd.action(async ({ session, args }, rawIndex) => {
|
|
242
262
|
if (!config.allowPrivate && !session.guildId) {
|
|
243
|
-
return '
|
|
263
|
+
return '\u4e0d\u652f\u6301\u5728\u79c1\u804a\u4e2d\u67e5\u8be2\u3002';
|
|
244
264
|
}
|
|
245
265
|
|
|
246
266
|
const service = ctx.chatluna_character;
|
|
247
|
-
if (!service) return 'chatluna-character
|
|
267
|
+
if (!service) return 'chatluna-character \u672a\u52a0\u8f7d\u3002';
|
|
248
268
|
|
|
249
269
|
const temp = await service.getTemp(session);
|
|
250
270
|
const targetIndex = parseIndex(rawIndex ?? args?.[0]);
|
|
251
271
|
|
|
252
|
-
// 1)
|
|
272
|
+
// 1) \u4f18\u5148\u8bfb\u53d6\u6700\u65b0\u4e00\u6b21\u539f\u59cb\u54cd\u5e94\uff08\u901a\u5e38\u4ecd\u542b <think>\uff09\uff0c\u4ec5\u5bf9\u7b2c 1 \u6761\u6709\u6548
|
|
253
273
|
const thinkFromRaw = targetIndex === 1 ? getLatestRawThink(temp) : '';
|
|
254
274
|
|
|
255
|
-
// 2)
|
|
275
|
+
// 2) \u5386\u53f2 completionMessages \u4e2d\u771f\u6b63\u5e26 <think> \u7684 AI \u6d88\u606f
|
|
256
276
|
const messages = temp?.completionMessages || [];
|
|
257
277
|
const thinkFromHistory = thinkFromRaw ? '' : getNthThink(messages, targetIndex);
|
|
258
278
|
|
|
259
|
-
// 3)
|
|
279
|
+
// 3) \u56de\u9000\uff1a\u7b2c N \u6761 AI \u6d88\u606f\u518d\u5c1d\u8bd5\u62bd\u53d6 <think>
|
|
260
280
|
const fallbackMsg = thinkFromRaw || thinkFromHistory ? null : getNthAiMessage(messages, targetIndex);
|
|
261
281
|
const think = thinkFromRaw || thinkFromHistory || extractThink(extractText(fallbackMsg?.content));
|
|
262
282
|
const formatted = formatThink(think);
|
|
@@ -264,7 +284,7 @@ function apply(ctx, config) {
|
|
|
264
284
|
|
|
265
285
|
if (config.renderImage && ctx.chatluna?.renderer) {
|
|
266
286
|
try {
|
|
267
|
-
const title = `###
|
|
287
|
+
const title = `### \u4e0a\u4e00\u6761\u601d\u8003\uff08\u5012\u6570\u7b2c ${targetIndex} \u6761\uff09`;
|
|
268
288
|
const markdown = `<div align="center">\n${title}\n</div>\n\n<div align="left">\n${formatted}\n</div>`;
|
|
269
289
|
const rendered = await ctx.chatluna.renderer.render(
|
|
270
290
|
{
|
|
@@ -278,10 +298,10 @@ function apply(ctx, config) {
|
|
|
278
298
|
}
|
|
279
299
|
}
|
|
280
300
|
|
|
281
|
-
return
|
|
301
|
+
return `\u4e0a\u4e00\u6761\u601d\u8003\uff08\u5012\u6570\u7b2c ${targetIndex} \u6761\uff09\n${formatted}`;
|
|
282
302
|
});
|
|
283
303
|
|
|
284
|
-
//
|
|
304
|
+
// \u5f02\u5e38\u8f93\u51fa\u81ea\u52a8\u5904\u7406
|
|
285
305
|
applyGuard(ctx, config);
|
|
286
306
|
}
|
|
287
307
|
|
|
@@ -290,4 +310,4 @@ module.exports = {
|
|
|
290
310
|
apply,
|
|
291
311
|
Config,
|
|
292
312
|
inject,
|
|
293
|
-
};
|
|
313
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-chatluna-think-viewer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"description": "View chatluna <think> blocks and auto recall abnormal formatted replies.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"koishi": {
|
|
29
29
|
"description": {
|
|
30
|
-
"zh": "
|
|
31
|
-
"en": "View <think> blocks from chatluna-character and auto recall malformed replies."
|
|
30
|
+
"zh": "通过命令/关键词查看 chatluna-character 最近一次回复的 <think> 思考内容,并在消息格式异常时自动拦截/撤回。",
|
|
31
|
+
"en": "View <think> blocks from chatluna-character and auto recall/guard malformed replies."
|
|
32
32
|
},
|
|
33
33
|
"service": {
|
|
34
34
|
"required": [
|
|
@@ -40,5 +40,4 @@
|
|
|
40
40
|
"index.js",
|
|
41
41
|
"README.md"
|
|
42
42
|
]
|
|
43
|
-
}
|
|
44
|
-
|
|
43
|
+
}
|