mioku-plugin-chat 3.0.6 → 3.1.1

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.
@@ -2,6 +2,7 @@ import {logger} from "mioku";
2
2
  import type { AITool } from "mioku";
3
3
  import { TOOL_RESULT_FOLLOWUP_KEY } from "mioku";
4
4
  import type { ToolContext } from "../../types";
5
+ import { mainModelSupportsVision } from "../../utils/model";
5
6
 
6
7
  async function createImageFollowupResult(
7
8
  imageUrl: string,
@@ -67,7 +68,11 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
67
68
  parameters: {
68
69
  type: "object",
69
70
  properties: {
70
- user_id: { type: "number", description: "QQ number of the member" },
71
+ user_id: {
72
+ type: "string",
73
+ description:
74
+ "Platform user id of the member (QQ number, or openid on QQ official)",
75
+ },
71
76
  },
72
77
  required: ["user_id"],
73
78
  },
@@ -137,7 +142,7 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
137
142
  type: "object",
138
143
  properties: {
139
144
  message_id: {
140
- type: "number",
145
+ type: "string",
141
146
  description:
142
147
  "The message ID (message_id) of the image or video. You can get this from the original message that contains the media.",
143
148
  },
@@ -154,7 +159,7 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
154
159
  }
155
160
 
156
161
  if (media.kind === "image") {
157
- if (toolCtx.config.isMultimodal) {
162
+ if (mainModelSupportsVision(toolCtx.aiService, toolCtx.config.model)) {
158
163
  return await createImageFollowupResult(
159
164
  media.url,
160
165
  `The image from message #${args.message_id} is attached. Inspect it directly and answer the user's question from the visual content.`,
@@ -201,7 +206,8 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
201
206
 
202
207
  try {
203
208
  const attachFullVideo =
204
- toolCtx.config.isMultimodal && videoFile.byteSize <= VIDEO_FULL_UPLOAD_MAX_BYTES;
209
+ mainModelSupportsVision(toolCtx.aiService, toolCtx.config.model) &&
210
+ videoFile.byteSize <= VIDEO_FULL_UPLOAD_MAX_BYTES;
205
211
 
206
212
  if (attachFullVideo) {
207
213
  const fs = await import("fs/promises");
@@ -250,8 +256,9 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
250
256
  type: "object",
251
257
  properties: {
252
258
  user_id: {
253
- type: "number",
254
- description: "QQ number of the member whose avatar you want to view",
259
+ type: "string",
260
+ description:
261
+ "Platform user id of the member whose avatar you want to view",
255
262
  },
256
263
  },
257
264
  required: ["user_id"],
@@ -261,7 +268,7 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
261
268
  const avatarUrl = `https://q1.qlogo.cn/g?b=qq&nk=${args.user_id}&s=640`;
262
269
  logger.info(`[view_member_avatar] Analyzing avatar: ${avatarUrl}`);
263
270
 
264
- if (toolCtx.config.isMultimodal) {
271
+ if (mainModelSupportsVision(toolCtx.aiService, toolCtx.config.model)) {
265
272
  return await createImageFollowupResult(
266
273
  avatarUrl,
267
274
  `User ${args.user_id}'s QQ avatar is attached. Inspect it directly and answer the user's question from the visual content.`,
package/core/tools/web.ts CHANGED
@@ -23,14 +23,11 @@ function resolveUserHistoryLimit(value: unknown): number {
23
23
 
24
24
  function extractTargetUserIdsFromQuestion(
25
25
  question: string,
26
- requesterUserId: number,
27
- ): number[] {
26
+ requesterUserId: string,
27
+ ): string[] {
28
28
  const matches = question.match(/\b\d{5,12}\b/g) || [];
29
- const parsed = matches
30
- .map((item) => Number(item))
31
- .filter((item) => Number.isFinite(item) && item > 0);
32
- const ids = [requesterUserId, ...parsed].filter(
33
- (item) => Number.isFinite(item) && item > 0,
29
+ const ids = [String(requesterUserId ?? "").trim(), ...matches].filter(
30
+ (item) => item.length > 0,
34
31
  );
35
32
  return [...new Set(ids)].slice(0, 3);
36
33
  }
@@ -138,8 +135,8 @@ async function fetchGroupHistoryByMessageIdPaging(
138
135
  sessionId: toolCtx.sessionId,
139
136
  role: String(raw.user_id) === String(bot.bot_id) ? "assistant" : "user",
140
137
  content,
141
- userId: Number(raw.user_id || 0),
142
- userName: raw.nickname || String(raw.user_id || "unknown"),
138
+ userId: String(raw.user_id ?? "").trim(),
139
+ userName: raw.nickname || String(raw.user_id ?? "unknown"),
143
140
  userRole: "member",
144
141
  groupId: toolCtx.groupId,
145
142
  timestamp: ts,
package/db.ts CHANGED
@@ -69,14 +69,14 @@ export interface ChatDatabase {
69
69
  before?: number,
70
70
  ): ChatMessage[];
71
71
  // 获取 bot 发送的消息
72
- getBotMessages(groupId: number, limit?: number): ChatMessage[];
73
- getStoredGroupNoticeMessages(groupId: number, limit?: number): ChatMessage[];
72
+ getBotMessages(groupId: string, limit?: number): ChatMessage[];
73
+ getStoredGroupNoticeMessages(groupId: string, limit?: number): ChatMessage[];
74
74
  getMessagesByUser(
75
- userId: number,
75
+ userId: string,
76
76
  sessionId?: string,
77
77
  limit?: number,
78
78
  ): ChatMessage[];
79
- getAllMessagesByUser(userId: number, sessionId?: string): ChatMessage[];
79
+ getAllMessagesByUser(userId: string, sessionId?: string): ChatMessage[];
80
80
  getMessagesByTimeRange(
81
81
  sessionId: string,
82
82
  startTimestamp: number,
@@ -108,9 +108,9 @@ export interface ChatDatabase {
108
108
  // 表达学习
109
109
  saveExpression(expr: ExpressionRecord): void;
110
110
  getExpressions(sessionId: string, limit?: number): ExpressionRecord[];
111
- getExpressionsByUser(userId: number, limit?: number): ExpressionRecord[];
111
+ getExpressionsByUser(userId: string, limit?: number): ExpressionRecord[];
112
112
  replaceExpressionsByUser(
113
- userId: number,
113
+ userId: string,
114
114
  userName: string,
115
115
  expressions: Array<
116
116
  Pick<ExpressionRecord, "situation" | "style" | "example">
@@ -473,7 +473,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
473
473
  .reverse(); // 按时间正序
474
474
  },
475
475
 
476
- getBotMessages(groupId: number, limit: number = 50): ChatMessage[] {
476
+ getBotMessages(groupId: string, limit: number = 50): ChatMessage[] {
477
477
  const rows = stmts.getBotMessages.all({ $groupId: groupId, $limit: limit }) as any[];
478
478
  return rows
479
479
  .map(toChatMessage)
@@ -481,7 +481,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
481
481
  },
482
482
 
483
483
  getStoredGroupNoticeMessages(
484
- groupId: number,
484
+ groupId: string,
485
485
  limit: number = 20,
486
486
  ): ChatMessage[] {
487
487
  const rows = stmts.getStoredGroupNoticeMessages.all(
@@ -493,7 +493,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
493
493
  },
494
494
 
495
495
  getMessagesByUser(
496
- userId: number,
496
+ userId: string,
497
497
  sessionId?: string,
498
498
  limit: number = 20,
499
499
  ): ChatMessage[] {
@@ -510,7 +510,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
510
510
  .reverse();
511
511
  },
512
512
 
513
- getAllMessagesByUser(userId: number, sessionId?: string): ChatMessage[] {
513
+ getAllMessagesByUser(userId: string, sessionId?: string): ChatMessage[] {
514
514
  const rows = sessionId
515
515
  ? (stmts.getAllMessagesByUserInSession.all({ $userId: userId, $sessionId: sessionId }) as any[])
516
516
  : (stmts.getAllMessagesByUser.all({ $userId: userId }) as any[]);
@@ -633,7 +633,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
633
633
  },
634
634
 
635
635
  getExpressionsByUser(
636
- userId: number,
636
+ userId: string,
637
637
  limit: number = 50,
638
638
  ): ExpressionRecord[] {
639
639
  const rows = stmts.getExpressionsByUser.all({ $userId: userId, $limit: limit }) as any[];
@@ -641,7 +641,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
641
641
  },
642
642
 
643
643
  replaceExpressionsByUser(
644
- userId: number,
644
+ userId: string,
645
645
  userName: string,
646
646
  expressions: Array<
647
647
  Pick<ExpressionRecord, "situation" | "style" | "example">
@@ -650,7 +650,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
650
650
  const now = Date.now();
651
651
  const tx = db.transaction(
652
652
  (
653
- targetUserId: number,
653
+ targetUserId: string,
654
654
  targetUserName: string,
655
655
  rows: Array<
656
656
  Pick<ExpressionRecord, "situation" | "style" | "example">
@@ -17,10 +17,10 @@ export async function handleIdleCheckDebug(
17
17
  return;
18
18
  }
19
19
  const groupIdStr =
20
- e.message[0]?.text?.replace("/空闲检查", "")?.trim() || "";
21
- const targetGroupId = parseInt(groupIdStr, 10);
20
+ e.message[0]?.text?.replace(/^[./]空闲检查/, "")?.trim() || "";
21
+ const targetGroupId = String(groupIdStr).trim();
22
22
  if (!targetGroupId) {
23
- await e.reply("请指定群号,如:/空闲检查 123456");
23
+ await e.reply("请指定群 ID,如:.空闲检查 123456");
24
24
  return;
25
25
  }
26
26
 
@@ -61,7 +61,7 @@ export async function handleIdleCheckDebug(
61
61
 
62
62
  const targetMessage: TargetMessage = {
63
63
  userName: "系统",
64
- userId: 0,
64
+ userId: "",
65
65
  userRole: "member",
66
66
  content: "[No one in the group is talking? I'll answer!]",
67
67
  messageId: "",
@@ -73,7 +73,7 @@ export async function handleIdleCheckDebug(
73
73
  event: null,
74
74
  groupSessionId,
75
75
  groupId: targetGroupId,
76
- userId: 0,
76
+ userId: "",
77
77
  config: cfg,
78
78
  aiService: ctx.services!.ai as AIService,
79
79
  db: pluginCtx.db,
@@ -117,8 +117,8 @@ export async function handleIdleCheckDebug(
117
117
  result,
118
118
  groupId: targetGroupId,
119
119
  groupSessionId,
120
- userId: 0,
121
- selfId: e.self_id,
120
+ userId: "",
121
+ selfId: String(e.self_id ?? "").trim(),
122
122
  toolCtx,
123
123
  send: true,
124
124
  isLive: true,
@@ -35,8 +35,8 @@ export function createMessageHandler(
35
35
 
36
36
  return async (e: MessageEvent) => {
37
37
  const isGroup = e.message_type === "group";
38
- const groupId: number | undefined = isGroup
39
- ? Number(e.group_id)
38
+ const groupId: string | undefined = isGroup
39
+ ? String(e.group_id ?? "").trim() || undefined
40
40
  : undefined;
41
41
  const cfg = await getConfig(groupId);
42
42
  if (!isGroup && cfg.ignorePrivateChat) return;
@@ -44,13 +44,13 @@ export function createMessageHandler(
44
44
  if (!e?.message || !Array.isArray(e.message)) return;
45
45
 
46
46
  const text = ctx.text(e) || "";
47
- const userId: number = Number(e.user_id || e.sender?.user_id || 0);
47
+ const userId: string = String(e.user_id ?? e.sender?.user_id ?? "").trim();
48
48
 
49
- if (userId === Number(e.self_id || 0)) return;
49
+ if (!userId || userId === String(e.self_id ?? "").trim()) return;
50
50
 
51
51
  if (matchMessageCommands && matchMessageCommands(text).length > 0) return;
52
52
 
53
- if (text.startsWith("/空闲检查 ")) {
53
+ if (/^[./]空闲检查\s/.test(text)) {
54
54
  await handleIdleCheckDebug(pluginCtx, e, cfg);
55
55
  return;
56
56
  }
@@ -204,9 +204,7 @@ export function createMessageHandler(
204
204
 
205
205
  const atBot = shouldTrigger(e, text, cfg, ctx);
206
206
  const replyBot = ctx.pickReplyBot(e);
207
- const actorSelfId = replyBot
208
- ? Number(replyBot.bot_id)
209
- : Number(e.self_id || 0);
207
+ const actorSelfId = String(replyBot?.bot_id ?? e.self_id ?? "").trim();
210
208
  const quotedBot = isGroup ? await isQuotingBot(e, ctx) : null;
211
209
  const mentionedNickname =
212
210
  cfg.nicknames.length > 0 &&
package/handlers/poke.ts CHANGED
@@ -15,12 +15,13 @@ export function createPokeHandler(
15
15
  const { getConfig, runtimeState, pokeCooldowns } = state;
16
16
 
17
17
  return async (e: NoticeEvent) => {
18
- const selfId = Number(e.self_id || 0);
19
- const targetId = Number(
20
- (e.raw as { target_id?: string } | undefined)?.target_id ?? 0,
21
- );
22
- if (targetId !== selfId) return;
23
- const groupId = Number(e.group_id || 0);
18
+ const selfId = String(e.self_id ?? "").trim();
19
+ const raw = e.raw as { target_id?: unknown; user_id?: unknown } | undefined;
20
+ const targetId = String(
21
+ raw?.target_id ?? raw?.user_id ?? e.user_id ?? "",
22
+ ).trim();
23
+ if (!targetId || targetId !== selfId) return;
24
+ const groupId = String(e.group_id ?? "").trim();
24
25
  const cfg = groupId ? await getConfig(groupId) : await getConfig();
25
26
  if (!cfg.model && !cfg.apiKey) return;
26
27
  if (!groupId || !isGroupAllowed(groupId, cfg)) return;
@@ -42,10 +43,9 @@ export function createPokeHandler(
42
43
  "group",
43
44
  groupId,
44
45
  );
45
- const userId = Number(e.user_id || 0);
46
+ const userId = String(e.user_id ?? "").trim();
46
47
  const botRole = await getBotRole(groupId, ctx, selfId);
47
- const botNickname =
48
- cfg.nicknames[0] || e.bot?.nickname || "Bot";
48
+ const botNickname = cfg.nicknames[0] || e.bot?.nickname || "Bot";
49
49
 
50
50
  let senderName = String(userId);
51
51
  try {
@@ -54,9 +54,7 @@ export function createPokeHandler(
54
54
  ? await bot.getMemberInfo(groupId, userId)
55
55
  : undefined;
56
56
  senderName =
57
- memberInfo?.card ||
58
- memberInfo?.nickname ||
59
- String(userId);
57
+ memberInfo?.card || memberInfo?.nickname || String(userId);
60
58
  } catch {}
61
59
 
62
60
  const targetMessage: TargetMessage = {
@@ -76,8 +74,11 @@ export function createPokeHandler(
76
74
  selfId,
77
75
  pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
78
76
  );
79
- const { groupName, memberCount } =
80
- await pluginCtx.getGroupInfoData(ctx, groupId, selfId);
77
+ const { groupName, memberCount } = await pluginCtx.getGroupInfoData(
78
+ ctx,
79
+ groupId,
80
+ selfId,
81
+ );
81
82
 
82
83
  const toolCtx = pluginCtx.buildToolContext({
83
84
  ctx,
@@ -38,7 +38,7 @@ export class EmotionAgent {
38
38
  return state;
39
39
  }
40
40
 
41
- getAvailableEmotions(groupId?: number): string[] {
41
+ getAvailableEmotions(groupId?: string): string[] {
42
42
  const cfg = this.getConfig(groupId);
43
43
  const emotions = Object.keys(cfg.emotion?.emotions || {})
44
44
  .map((name) => name.trim().toLowerCase())
@@ -46,7 +46,7 @@ export class EmotionAgent {
46
46
  return Array.from(new Set(["default", ...emotions]));
47
47
  }
48
48
 
49
- getReferenceExamples(emotion: string, groupId?: number): string[] {
49
+ getReferenceExamples(emotion: string, groupId?: string): string[] {
50
50
  const cfg = this.getConfig(groupId);
51
51
  const normalized = this.normalizeEmotionName(emotion);
52
52
  const emotions = cfg.emotion?.emotions || {};
@@ -55,7 +55,7 @@ export class EmotionAgent {
55
55
  return this.normalizeExamples(emotions[this.getDefaultEmotion(groupId)]?.examples);
56
56
  }
57
57
 
58
- setEmotion(sessionId: string, emotion: string, groupId?: number): EmotionState {
58
+ setEmotion(sessionId: string, emotion: string, groupId?: string): EmotionState {
59
59
  const current = this.resolveEmotion(emotion, groupId);
60
60
  const state = { current, updatedAt: Date.now() };
61
61
  this.states.set(sessionId, state);
@@ -144,14 +144,14 @@ ${input.targetMessage.userName}: ${input.targetMessage.content}`;
144
144
  return this.resolveEmotion(parsed?.emotion, extractGroupIdFromSession(input.sessionId));
145
145
  }
146
146
 
147
- private resolveEmotion(emotion: unknown, groupId?: number): string {
147
+ private resolveEmotion(emotion: unknown, groupId?: string): string {
148
148
  const normalized = this.normalizeEmotionName(emotion);
149
149
  const available = this.getAvailableEmotions(groupId);
150
150
  if (available.includes(normalized)) return normalized;
151
151
  return this.getDefaultEmotion(groupId);
152
152
  }
153
153
 
154
- private getDefaultEmotion(groupId?: number): string {
154
+ private getDefaultEmotion(groupId?: string): string {
155
155
  const cfg = this.getConfig(groupId);
156
156
  const configured = this.normalizeEmotionName(cfg.emotion?.defaultEmotion);
157
157
  const available = this.getAvailableEmotions(groupId);
@@ -9,8 +9,8 @@ export class ExpressionLearner {
9
9
  private ai: AIInstance;
10
10
  private getConfig: ChatConfigProvider;
11
11
  private db: ChatDatabase;
12
- private pendingMessagesByUser: Map<number, ChatMessage[]> = new Map();
13
- private learningUsers: Set<number> = new Set();
12
+ private pendingMessagesByUser: Map<string, ChatMessage[]> = new Map();
13
+ private learningUsers: Set<string> = new Set();
14
14
 
15
15
  constructor(ai: AIInstance, configProvider: ChatConfigProvider, db: ChatDatabase) {
16
16
  this.ai = ai;
@@ -33,9 +33,9 @@ export class ExpressionLearner {
33
33
  }
34
34
 
35
35
  getExpressionContextForUser(
36
- userId: number,
36
+ userId: string,
37
37
  userName: string,
38
- groupId?: number,
38
+ groupId?: string,
39
39
  ): string {
40
40
  const cfg = this.getConfig(groupId);
41
41
  if (!cfg.expression?.enabled) return "";
@@ -62,7 +62,7 @@ export class ExpressionLearner {
62
62
  return context;
63
63
  }
64
64
 
65
- private async tryLearn(userId: number, cfg = this.getConfig()): Promise<void> {
65
+ private async tryLearn(userId: string, cfg = this.getConfig()): Promise<void> {
66
66
  if (this.learningUsers.has(userId)) return;
67
67
 
68
68
  const threshold = Math.max(
@@ -95,7 +95,7 @@ export class ExpressionLearner {
95
95
  }
96
96
 
97
97
  private async learnForUser(
98
- userId: number,
98
+ userId: string,
99
99
  messages: ChatMessage[],
100
100
  cfg = this.getConfig(),
101
101
  ): Promise<void> {
package/humanize/index.ts CHANGED
@@ -16,7 +16,7 @@ import { TopicTracker } from "./topic";
16
16
  import { ActionPlanner } from "./planner";
17
17
  import { ExpressionLearner } from "./expression";
18
18
 
19
- export type ChatConfigProvider = (groupId?: number) => ChatConfig;
19
+ export type ChatConfigProvider = (groupId?: string) => ChatConfig;
20
20
 
21
21
  export class HumanizeEngine {
22
22
  readonly memoryRetrieval: MemoryRetrieval;
@@ -6,7 +6,7 @@ import { extractGroupIdFromSession } from "../utils/group-config";
6
6
  import type { ChatConfigProvider } from "./index";
7
7
 
8
8
  export interface MemoryUserHistoryChunk {
9
- userId: number;
9
+ userId: string;
10
10
  messages: ChatMessage[];
11
11
  }
12
12
 
package/index.ts CHANGED
@@ -48,6 +48,7 @@ import { buildHistoryMediaOptions } from "./core/media/segment";
48
48
  import type { ChatConfigProvider } from "./humanize";
49
49
  import type { ChatPluginContext, ChatHandlerState } from "./context";
50
50
  import { mergeGroupOverrides } from "./utils/group-config";
51
+ import { mainModelSupportsVision } from "./utils/model";
51
52
  import {
52
53
  mergeChatConfig,
53
54
  normalizeIdList,
@@ -95,9 +96,20 @@ function resolveRoleInstances(aiService: AIService): {
95
96
  const visionModel = findModel("vision", vision) || workingModel;
96
97
 
97
98
  const models = aiService.listModels?.() ?? [];
98
- const visionDesc =
99
- models.find((item) => item.id === bindings.vision) ||
100
- models.find((item) => item.modelId === visionModel);
99
+ const findModelDesc = (role: AIModelRole, modelId: string) => {
100
+ const exact = models.find((item) => item.id === bindings[role]);
101
+ if (exact) return exact;
102
+ const providerId = instances.find(
103
+ (info) => info.role === role || info.name === role,
104
+ )?.providerId;
105
+ return (
106
+ models.find(
107
+ (item) => item.modelId === modelId && item.providerId === providerId,
108
+ ) || models.find((item) => item.modelId === modelId)
109
+ );
110
+ };
111
+
112
+ const visionDesc = findModelDesc("vision", visionModel);
101
113
  const isMultimodal =
102
114
  visionDesc?.capabilities?.includes("vision") ?? Boolean(visionModel);
103
115
 
@@ -261,7 +273,7 @@ export default definePlugin({
261
273
  });
262
274
  }
263
275
 
264
- const configProvider: ChatConfigProvider = (groupId?: number) => {
276
+ const configProvider: ChatConfigProvider = (groupId?: string) => {
265
277
  const base = cachedBaseConfig;
266
278
  if (!base) return PERSONALIZATION_CONFIG as unknown as ChatConfig;
267
279
  if (groupId === undefined) return base;
@@ -269,7 +281,7 @@ export default definePlugin({
269
281
  return mergeGroupOverrides(base, overrides);
270
282
  };
271
283
 
272
- const getConfig = async (groupId?: number): Promise<ChatConfig> => {
284
+ const getConfig = async (groupId?: string): Promise<ChatConfig> => {
273
285
  if (!cachedBaseConfig) await refreshBaseCache();
274
286
  return configProvider(groupId);
275
287
  };
@@ -341,7 +353,7 @@ export default definePlugin({
341
353
  return;
342
354
  }
343
355
 
344
- roleModels = resolved.models;
356
+ roleModels = resolved.models;
345
357
  roleIsMultimodal = resolved.isMultimodal;
346
358
  await refreshBaseCache();
347
359
 
@@ -380,14 +392,14 @@ roleModels = resolved.models;
380
392
  rateLimiter.setConfigProvider(configProvider);
381
393
  const skillManager = new SkillSessionManager();
382
394
 
383
- rateLimiter.setQueueLengthGetter((groupId: number) =>
395
+ rateLimiter.setQueueLengthGetter((groupId: string) =>
384
396
  queueManager.getQueueLength(`group:${groupId}`),
385
397
  );
386
398
 
387
399
  const humanize = new HumanizeEngine(workAIInstance, db, configProvider);
388
400
  await humanize.init();
389
401
 
390
- const pokeCooldowns = new Map<number, number>();
402
+ const pokeCooldowns = new Map<string, number>();
391
403
  const processingSet = new Set<string>();
392
404
  const sessionTurnScheduler = new SessionTurnScheduler();
393
405
  const groupStructuredHistory = new GroupStructuredHistoryManager();
@@ -442,8 +454,8 @@ roleModels = resolved.models;
442
454
  audioService,
443
455
  startCooldownTimer: (
444
456
  groupSessionId: string,
445
- groupId: number,
446
- selfId: number,
457
+ groupId: string,
458
+ selfId: string,
447
459
  ) => cooldownManager.startCooldownTimer(groupSessionId, groupId, selfId),
448
460
  async recordGroupMessageForLearning(
449
461
  userMsg: ChatMessage,
@@ -511,20 +523,25 @@ roleModels = resolved.models;
511
523
  aiService.registerChatRuntime(runtime);
512
524
 
513
525
  ctx.command({
514
- name: "/重置会话",
515
- match: /^\/重置会话$/,
516
- prefixes: false,
526
+ name: "重置会话",
527
+ prefixes: ["/", "."],
528
+ match: /^重置会话$/,
517
529
  description: "重置自己的AI聊天记录",
530
+ usage: ".重置会话",
518
531
  handler: async ({ event }) => {
519
- if (event.user_id === event.self_id) return;
532
+ if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
520
533
  const groupId =
521
- event.message_type === "group" ? Number(event.group_id) : undefined;
534
+ event.message_type === "group"
535
+ ? String(event.group_id ?? "").trim() || undefined
536
+ : undefined;
522
537
  if (groupId) {
523
538
  pluginCtx.sessionManager.resetBotMessages(`group:${groupId}`);
524
539
  pluginCtx.groupStructuredHistory.clear(`group:${groupId}`);
525
540
  await event.reply("已清除本群会话中 AI 发送的消息~");
526
541
  } else {
527
- const userId = Number(event.user_id || event.sender?.user_id || 0);
542
+ const userId = String(
543
+ event.user_id ?? event.sender?.user_id ?? "",
544
+ ).trim();
528
545
  pluginCtx.sessionManager.resetBotMessages(`personal:${userId}`);
529
546
  pluginCtx.groupStructuredHistory.clear(`personal:${userId}`);
530
547
  await event.reply("已清除你的个人会话中 AI 发送的消息~");
@@ -532,15 +549,18 @@ roleModels = resolved.models;
532
549
  },
533
550
  });
534
551
  ctx.command({
535
- name: "/重置群会话",
536
- match: /^\/重置群会话$/,
537
- prefixes: false,
552
+ name: "重置群会话",
553
+ prefixes: ["/", "."],
554
+ match: /^重置群会话$/,
538
555
  permission: "admin",
539
556
  description: "重置当前群的AI聊天记录",
557
+ usage: ".重置群会话",
540
558
  handler: async ({ event }) => {
541
- if (event.user_id === event.self_id) return;
559
+ if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
542
560
  const groupId =
543
- event.message_type === "group" ? Number(event.group_id) : undefined;
561
+ event.message_type === "group"
562
+ ? String(event.group_id ?? "").trim() || undefined
563
+ : undefined;
544
564
  if (!groupId) {
545
565
  await event.reply("在群里使用试试看吧~", true);
546
566
  return;
@@ -551,15 +571,19 @@ roleModels = resolved.models;
551
571
  },
552
572
  });
553
573
  ctx.command({
554
- name: "/tts",
555
- match: /^\/tts\s*(.*)$/,
556
- prefixes: false,
574
+ name: "tts",
575
+ prefixes: ["/", "."],
576
+ match: /^tts\s*(.*)$/,
557
577
  permission: "owner",
558
578
  description: "TTS 推理",
559
- usage: "/tts <文本>",
579
+ usage: ".tts <文本>",
560
580
  handler: async ({ event, match }) => {
561
- if (event.user_id === event.self_id) return;
562
- await handleTtsCommand(pluginCtx, event, String(match?.[1] ?? "").trim());
581
+ if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
582
+ await handleTtsCommand(
583
+ pluginCtx,
584
+ event,
585
+ String(match?.[1] ?? "").trim(),
586
+ );
563
587
  },
564
588
  });
565
589
 
@@ -567,7 +591,7 @@ roleModels = resolved.models;
567
591
  ctx.handle("notice.group.poke", createPokeHandler(pluginCtx, handlerState));
568
592
 
569
593
  ctx.logger.info(
570
- `聊天插件加载成功 (main=${roleModels.main || "?"}, work=${roleModels.working || "?"}, vision=${roleModels.vision || "?"})`,
594
+ `聊天插件加载成功 (main=${roleModels.main || "?"}${mainModelSupportsVision(aiService, roleModels.main) ? "" : " 无视觉,图片交给视觉模型描述"}, work=${roleModels.working || "?"}, vision=${roleModels.vision || "?"})`,
571
595
  );
572
596
 
573
597
  return () => {
@@ -26,7 +26,7 @@ interface CooldownMessage {
26
26
  event: any;
27
27
  content: string;
28
28
  userName: string;
29
- userId: number;
29
+ userId: string;
30
30
  messageId: string;
31
31
  timestamp: number;
32
32
  isDirectAt: boolean;
@@ -86,8 +86,8 @@ export class CooldownManager {
86
86
 
87
87
  startCooldownTimer(
88
88
  groupSessionId: string,
89
- groupId: number,
90
- selfId: number,
89
+ groupId: string,
90
+ selfId: string,
91
91
  ): void {
92
92
  const existingTimer = this.cooldownTimeoutIds.get(groupSessionId);
93
93
  if (existingTimer) clearTimeout(existingTimer);
@@ -142,7 +142,7 @@ export class CooldownManager {
142
142
 
143
143
  collectMessage(
144
144
  groupSessionId: string,
145
- groupId: number,
145
+ groupId: string,
146
146
  event: any,
147
147
  content: string,
148
148
  isDirectAt: boolean,
@@ -175,9 +175,9 @@ export class CooldownManager {
175
175
 
176
176
  private async processReviewMessages(
177
177
  groupSessionId: string,
178
- groupId: number,
178
+ groupId: string,
179
179
  collected: CooldownMessage[],
180
- selfId: number,
180
+ selfId: string,
181
181
  ): Promise<void> {
182
182
  const mergedContents = collected.map((m) => m.content);
183
183
  const userNames = collected.map((m) => m.userName);
@@ -297,9 +297,9 @@ export class CooldownManager {
297
297
 
298
298
  private async processCooldownWithPlanner(
299
299
  groupSessionId: string,
300
- groupId: number,
300
+ groupId: string,
301
301
  collected: CooldownMessage[],
302
- selfId: number,
302
+ selfId: string,
303
303
  ): Promise<void> {
304
304
  const mergedContent = collected.map((m) => m.content).join("\n");
305
305
  const firstMsg = collected[0];