mioku-plugin-chat 2.0.0 → 2.1.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.
@@ -38,7 +38,6 @@ export const PERSONALIZATION_CONFIG: {
38
38
  baseStyle:
39
39
  "Casual and cute, uses emoticons like >_< and ^_^, can occasionally mix in a small amount of natural everyday Japanese words like すごい、なるほど, but should not heavily rely on Japanese. Do not end sentences with commas or periods.",
40
40
  multipleStyles: [
41
- "Super hyper, lots of exclamation marks and excitement!!!",
42
41
  "Play dumb mode, like giving the opposite answer to the user's questions and saying it seriously",
43
42
  "Playing cute, likes to add 'w' at the end of cute phrases, commonly used to replace sentence-ending particles such as '呀'.",
44
43
  "Poetic and lyrical, speaks as if composing song lyrics",
@@ -5,6 +5,7 @@ export const SETTINGS_CONFIG = {
5
5
  timeoutMs: 8000,
6
6
  defaultLimit: 5,
7
7
  maxLimit: 8,
8
+ maxSearchCount: 2,
8
9
  },
9
10
  webReader: {
10
11
  enabled: true,
@@ -108,6 +108,7 @@ export async function runChat(
108
108
  const directImageUrls = toolCtx.config.isMultimodal
109
109
  ? toolCtx.pendingImageUrls
110
110
  : undefined;
111
+ const webSearchState = { count: 0 };
111
112
  const usageId = `chat:${toolCtx.sessionId}:${Date.now()}:${Math.random()
112
113
  .toString(36)
113
114
  .slice(2, 10)}`;
@@ -212,6 +213,7 @@ export async function runChat(
212
213
  skillManager.getTools(toolCtx.sessionId),
213
214
  toolCtx,
214
215
  runtimeOptions?.extraTools,
216
+ webSearchState,
215
217
  ),
216
218
  temperature: toolCtx.config.temperature,
217
219
  maxIterations: toolCtx.config.maxIterations,
@@ -240,6 +242,12 @@ export async function runChat(
240
242
 
241
243
  const allToolCalls = response.allToolCalls || [];
242
244
 
245
+ const maxSearchCount = toolCtx.config.searxng.maxSearchCount;
246
+ if (maxSearchCount > 0 && webSearchState.count >= maxSearchCount) {
247
+ const limitMsg = `[system] Maximum search limit (${maxSearchCount}) reached. Web search tool is now disabled. `;
248
+ response.content = limitMsg + (response.content || "");
249
+ }
250
+
243
251
  if (toolCtx.config.debug && response.reasoning) {
244
252
  logger.info(`[chat-engine] AI reasoning: ${response.reasoning}`);
245
253
  }
@@ -456,16 +464,26 @@ function buildSessionTools(
456
464
  skillTools: Map<string, AITool>,
457
465
  toolCtx: ToolContext,
458
466
  extraTools: AITool[] = [],
467
+ webSearchState?: { count: number },
459
468
  ): SessionToolDefinition[] {
469
+ const maxSearchCount = toolCtx.config.searxng.maxSearchCount;
470
+ const limitReached =
471
+ maxSearchCount > 0 && webSearchState && webSearchState.count >= maxSearchCount;
460
472
  const tools: SessionToolDefinition[] = [];
461
473
  const runtimeContext = createExternalSkillRuntimeContext(toolCtx);
462
474
 
463
475
  for (const tool of chatTools) {
476
+ if (tool.name === "web_search" && limitReached) continue;
464
477
  tools.push({
465
478
  name: tool.name,
466
479
  tool: {
467
480
  ...tool,
468
- handler: (args: any) => tool.handler(args, runtimeContext),
481
+ handler: (args: any) => {
482
+ if (tool.name === "web_search" && webSearchState) {
483
+ webSearchState.count++;
484
+ }
485
+ return tool.handler(args, runtimeContext);
486
+ },
469
487
  },
470
488
  });
471
489
  }
@@ -491,11 +509,21 @@ function buildSessionTools(
491
509
  continue;
492
510
  }
493
511
 
512
+ // builtin feature 走 loadSkill 后名前缀,也要受 maxSearchCount 限制。
513
+ if (name === "web_search.web_search" && limitReached) {
514
+ continue;
515
+ }
516
+
494
517
  tools.push({
495
518
  name,
496
519
  tool: {
497
520
  ...tool,
498
- handler: (args: any) => tool.handler(args, runtimeContext),
521
+ handler: (args: any) => {
522
+ if (name === "web_search.web_search" && webSearchState) {
523
+ webSearchState.count++;
524
+ }
525
+ return tool.handler(args, runtimeContext);
526
+ },
499
527
  },
500
528
  });
501
529
  }
@@ -180,24 +180,32 @@ export async function analyzeImage(
180
180
 
181
181
  Instructions:
182
182
  1. Classify the image as either "meme" or "image":
183
- - "meme": Images with anime/cartoon characters, usually expressing emotions or reactions
184
- - "image": Regular images conveying information
183
+ - "meme": A standalone, single-scene character image (anime/cartoon/VTuber etc.) used to express ONE specific emotion or reaction. Typical features: isolated character on a clean/simple background, exaggerated expression or pose, often with short punchline text. The character is the visual focus and the whole image serves as a sticker.
184
+ - "image": Any image whose primary purpose is to convey real-world information, not to be used as a single-emotion sticker. This includes (but is not limited to): real-life photos, scene photos, real-person selfies/portraits, screenshots, infographics, news/announcement images, product images, app UI screenshots, web page screenshots, and most importantly CHAT / IM INTERFACE SCREENSHOTS (QQ, WeChat, Discord, Telegram, LINE, message bubbles, chat history, etc.).
185
+
186
+ CRITICAL disambiguation rules — read carefully:
187
+ - Even if the image LOOKS like a meme at first glance (has characters, has text bubbles, has cute style), if it is actually a SCREENSHOT of a chat interface / messaging app / social app / browser / system UI, you MUST classify it as "image", NOT "meme". Telltale signs: status bar at top, navigation bar, input box, send button, message bubbles on both sides, timestamps, avatar lists, scroll bars, app headers/footers, system widgets.
188
+ - If the image is a screenshot where anime/cartoon characters appear (e.g. screenshot of a chat where someone sent a meme), the WHOLE image is still "image" — the screenshot context overrides the inner character content.
189
+ - If the image is a real-person photo, a selfie, a landscape photo, a real-world scene, an illustration that is NOT used as a reaction sticker (e.g. cover art, full-body standing pose with no expressive intent) — classify as "image".
190
+ - Only when the image is a single, clean, isolated character/scene intended to express ONE reaction (no surrounding UI, no real-world photo context, no chat interface) should you classify it as "meme".
191
+ - When in doubt, prefer "image" over "meme".
185
192
 
186
193
  2. Provide a brief description (max 30 words in Chinese):
187
- - For memes: First identify the character's name, then describe the character's status, actions, and the text near the image.
188
- - For images: Describe what you see, summarize the text you see (who did what and where).
194
+ - For memes: The description is used as the saved filename, so it MUST be meaningful in Chinese. First, identify the character and use their CHINESE display name in the description. Then describe the character's status / pose / expression / action and any short text on the image. Example: "初音未来开心地竖起大拇指说早上好" / "镜音铃害羞地捂住脸".
195
+ - For images: Describe what you see factually, summarize any visible text (who did what and where).
189
196
  ${imageUrls.length > 1 ? "\n - Note: You are viewing multiple frames from an animated image (GIF). Consider the overall motion and emotion across all frames." : ""}
190
197
 
191
198
  3. For memes only:
192
199
  - Emotion tag: ${EMOTION_TAGS.join(", ")} (choose ONLY ONE that best represents the overall mood)
193
- - Character names: Can have multiple characters (array format), use English names. Already known: ${KNOWN_CHARACTERS.join(", ")}. You CAN add new ones.
200
+ - Character names: Can have multiple characters (array format), use English identifiers. Already known: ${KNOWN_CHARACTERS.join(", ")}. You CAN add new ones.
201
+ - If you recognize a character that is NOT in the known list, add a new English identifier to "characters" AND include a reasonable Chinese display name in "description".
194
202
 
195
203
  Response format (JSON):
196
204
  {
197
205
  "type": "meme" or "image",
198
- "description": "brief description in Chinese",
199
- "emotion": "emotion tag (choose ONLY ONE)",
200
- "characters": ["character1", "character2", ...] (array of character names, can be empty or have multiple)
206
+ "description": "brief description in Chinese (for memes: must include the Chinese name of the character)",
207
+ "emotion": "emotion tag (choose ONLY ONE, only for memes)",
208
+ "characters": ["character1", "character2", ...] (array of English character identifiers, can be empty or have multiple; only for memes)
201
209
  }`;
202
210
 
203
211
  const userPrompt =
package/core/tools.ts CHANGED
@@ -685,7 +685,7 @@ function createWebSearchTool(toolCtx: ToolContext): AITool {
685
685
  return {
686
686
  name: "web_search",
687
687
  description:
688
- "Search the web using SearXNG. Use this for current events, external facts, documentation, or anything not in chat history.",
688
+ "Search the web using SearXNG. Use this for current events, external facts, documentation, or anything not in chat history. This tool can only be called a limited number of times per conversation.",
689
689
  parameters: {
690
690
  type: "object",
691
691
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-chat",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "AI 智能聊天插件",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -18,6 +18,13 @@
18
18
  "help",
19
19
  "screenshot"
20
20
  ],
21
+ "accessHooks": [
22
+ {
23
+ "id": "戳一戳触发对话",
24
+ "event": "notice.group.poke",
25
+ "description": "群内戳一戳 bot"
26
+ }
27
+ ],
21
28
  "help": {
22
29
  "title": "AI 聊天",
23
30
  "description": "智能 AI 聊天插件",
package/types.ts CHANGED
@@ -60,7 +60,6 @@ export interface PlannerConfig {
60
60
  idleCheckBotIds: number[];
61
61
  }
62
62
 
63
-
64
63
  /**
65
64
  * 表情包系统配置
66
65
  */
@@ -110,6 +109,8 @@ export interface SearxngConfig {
110
109
  defaultLimit: number;
111
110
  // 最大上限
112
111
  maxLimit: number;
112
+ // 允许的最大搜索次数
113
+ maxSearchCount: number;
113
114
  }
114
115
 
115
116
  /**