chatccc 0.2.47 → 0.2.49
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/package.json +1 -1
- package/src/__tests__/cards.test.ts +23 -8
- package/src/__tests__/feishu-platform.test.ts +56 -54
- package/src/__tests__/session.test.ts +2 -0
- package/src/cards.ts +3 -1
- package/src/feishu-api.ts +12 -0
- package/src/feishu-platform.ts +138 -133
- package/src/index.ts +35 -0
- package/src/session-chat-binding.ts +102 -86
- package/src/session.ts +109 -90
- package/src/sim-platform.ts +152 -147
- package/src/sim-store.ts +316 -312
package/src/sim-store.ts
CHANGED
|
@@ -1,313 +1,317 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sim-store.ts — 模拟飞书环境的状态管理核心
|
|
3
|
-
*
|
|
4
|
-
* SimStore 是一个单例的 EventEmitter,管理所有模拟状态:
|
|
5
|
-
* - 账户(bot + 多个用户)
|
|
6
|
-
* - 会话(群聊 + 私聊)
|
|
7
|
-
* - 消息历史(内存 + JSONL 持久化)
|
|
8
|
-
*
|
|
9
|
-
* 同时暴露 setMessageHandler / dispatchMessage,让 SimAgent 进程内触发
|
|
10
|
-
* handleCommand 而无需通过 HTTP。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { randomUUID } from "node:crypto";
|
|
14
|
-
import { appendFile, mkdir } from "node:fs/promises";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
-
import { EventEmitter } from "node:events";
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// 类型定义
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
export interface SimAccount {
|
|
24
|
-
id: string; // "bot" | "sim_user_001" | "alice"
|
|
25
|
-
kind: "bot" | "user";
|
|
26
|
-
name: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface SimMessage {
|
|
30
|
-
id: string; // UUID
|
|
31
|
-
chatId: string;
|
|
32
|
-
senderId: string; // SimAccount.id
|
|
33
|
-
type: "text" | "card" | "image" | "file" | "raw_card" | "system";
|
|
34
|
-
content: string; // 文本内容或 JSON 字符串
|
|
35
|
-
timestamp: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface SimChat {
|
|
39
|
-
id: string; // "sim_default" | "sim_<uuid8>" | "p2p_alice_bot"
|
|
40
|
-
type: "group" | "p2p";
|
|
41
|
-
name: string;
|
|
42
|
-
description: string;
|
|
43
|
-
memberIds: string[]; // 所有成员,第一个是创建者
|
|
44
|
-
messages: SimMessage[]; // 消息历史(按时间排序)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** handleCommand 的回调签名 */
|
|
48
|
-
export type MessageHandler = (
|
|
49
|
-
text: string,
|
|
50
|
-
chatId: string,
|
|
51
|
-
openId: string,
|
|
52
|
-
timestamp: number,
|
|
53
|
-
chatType: string,
|
|
54
|
-
traceId?: string,
|
|
55
|
-
) => Promise<void>;
|
|
56
|
-
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// 持久化路径
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
const SIM_DIR = join(homedir(), ".chatccc", "sim");
|
|
62
|
-
const MESSAGES_FILE = join(SIM_DIR, "messages.jsonl");
|
|
63
|
-
|
|
64
|
-
async function ensureDir(): Promise<void> {
|
|
65
|
-
await mkdir(SIM_DIR, { recursive: true });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function appendJsonl(line: Record<string, unknown>): Promise<void> {
|
|
69
|
-
await ensureDir();
|
|
70
|
-
await appendFile(MESSAGES_FILE, JSON.stringify(line) + "\n", "utf-8");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// SimStore
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
export class SimStore extends EventEmitter {
|
|
78
|
-
accounts = new Map<string, SimAccount>();
|
|
79
|
-
chats = new Map<string, SimChat>();
|
|
80
|
-
|
|
81
|
-
// ---- 初始化 ----
|
|
82
|
-
|
|
83
|
-
constructor() {
|
|
84
|
-
super();
|
|
85
|
-
this._initDefaults();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private _initDefaults(): void {
|
|
89
|
-
// 注册 bot 账户
|
|
90
|
-
this.accounts.set("bot", { id: "bot", kind: "bot", name: "ChatCCC" });
|
|
91
|
-
|
|
92
|
-
// 注册默认用户
|
|
93
|
-
this.accounts.set("sim_user_001", { id: "sim_user_001", kind: "user", name: "Developer" });
|
|
94
|
-
|
|
95
|
-
// 创建默认群聊
|
|
96
|
-
const defaultChatId = "sim_default";
|
|
97
|
-
this.chats.set(defaultChatId, {
|
|
98
|
-
id: defaultChatId,
|
|
99
|
-
type: "group",
|
|
100
|
-
name: "默认模拟会话",
|
|
101
|
-
description: "",
|
|
102
|
-
memberIds: ["bot", "sim_user_001"],
|
|
103
|
-
messages: [],
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---- 账户管理 ----
|
|
108
|
-
|
|
109
|
-
registerAccount(account: SimAccount): void {
|
|
110
|
-
if (this.accounts.has(account.id)) {
|
|
111
|
-
throw new Error(`Account "${account.id}" already exists`);
|
|
112
|
-
}
|
|
113
|
-
this.accounts.set(account.id, account);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
getAccount(id: string): SimAccount | undefined {
|
|
117
|
-
return this.accounts.get(id);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ---- 会话管理 ----
|
|
121
|
-
|
|
122
|
-
createGroupChat(name: string, memberIds: string[]): SimChat {
|
|
123
|
-
const chatId = `sim_${randomUUID().slice(0, 8)}`;
|
|
124
|
-
// bot 始终是成员
|
|
125
|
-
const allMembers = ["bot", ...memberIds.filter((id) => id !== "bot")];
|
|
126
|
-
const chat: SimChat = {
|
|
127
|
-
id: chatId,
|
|
128
|
-
type: "group",
|
|
129
|
-
name,
|
|
130
|
-
description: "",
|
|
131
|
-
memberIds: allMembers,
|
|
132
|
-
messages: [],
|
|
133
|
-
};
|
|
134
|
-
this.chats.set(chatId, chat);
|
|
135
|
-
|
|
136
|
-
// 通知被拉入群的用户
|
|
137
|
-
for (const uid of memberIds) {
|
|
138
|
-
this.emit("member_added", { chatId, userId: uid });
|
|
139
|
-
}
|
|
140
|
-
this.emit("chat_created", { chat });
|
|
141
|
-
|
|
142
|
-
return chat;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
createP2pChat(userId: string): SimChat {
|
|
146
|
-
// p2p 命名规则:p2p_<userId>_bot
|
|
147
|
-
const chatId = `p2p_${userId}_bot`;
|
|
148
|
-
const existing = this.chats.get(chatId);
|
|
149
|
-
if (existing) return existing;
|
|
150
|
-
|
|
151
|
-
const user = this.accounts.get(userId);
|
|
152
|
-
const chat: SimChat = {
|
|
153
|
-
id: chatId,
|
|
154
|
-
type: "p2p",
|
|
155
|
-
name: user ? `${user.name} 的私聊` : `私聊`,
|
|
156
|
-
description: "",
|
|
157
|
-
memberIds: ["bot", userId],
|
|
158
|
-
messages: [],
|
|
159
|
-
};
|
|
160
|
-
this.chats.set(chatId, chat);
|
|
161
|
-
this.emit("chat_created", { chat });
|
|
162
|
-
return chat;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
getChat(chatId: string): SimChat | undefined {
|
|
166
|
-
return this.chats.get(chatId);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getChatsForUser(userId: string): SimChat[] {
|
|
170
|
-
return [...this.chats.values()].filter((c) => c.memberIds.includes(userId));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
addMember(chatId: string, userId: string): void {
|
|
174
|
-
const chat = this.chats.get(chatId);
|
|
175
|
-
if (!chat) throw new Error(`Chat not found: ${chatId}`);
|
|
176
|
-
if (!chat.memberIds.includes(userId)) {
|
|
177
|
-
chat.memberIds.push(userId);
|
|
178
|
-
this.emit("member_added", { chatId, userId });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
updateChatInfo(chatId: string, name: string, description: string): void {
|
|
183
|
-
const chat = this.chats.get(chatId);
|
|
184
|
-
if (chat) {
|
|
185
|
-
chat.name = name;
|
|
186
|
-
chat.description = description;
|
|
187
|
-
} else {
|
|
188
|
-
// 模拟模式下机器人可能尝试更新不存在的群(比如从旧状态恢复),兼容处理
|
|
189
|
-
this.chats.set(chatId, {
|
|
190
|
-
id: chatId,
|
|
191
|
-
type: "group",
|
|
192
|
-
name,
|
|
193
|
-
description,
|
|
194
|
-
memberIds: ["bot", "sim_user_001"],
|
|
195
|
-
messages: [],
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
1
|
+
/**
|
|
2
|
+
* sim-store.ts — 模拟飞书环境的状态管理核心
|
|
3
|
+
*
|
|
4
|
+
* SimStore 是一个单例的 EventEmitter,管理所有模拟状态:
|
|
5
|
+
* - 账户(bot + 多个用户)
|
|
6
|
+
* - 会话(群聊 + 私聊)
|
|
7
|
+
* - 消息历史(内存 + JSONL 持久化)
|
|
8
|
+
*
|
|
9
|
+
* 同时暴露 setMessageHandler / dispatchMessage,让 SimAgent 进程内触发
|
|
10
|
+
* handleCommand 而无需通过 HTTP。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { EventEmitter } from "node:events";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// 类型定义
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export interface SimAccount {
|
|
24
|
+
id: string; // "bot" | "sim_user_001" | "alice"
|
|
25
|
+
kind: "bot" | "user";
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SimMessage {
|
|
30
|
+
id: string; // UUID
|
|
31
|
+
chatId: string;
|
|
32
|
+
senderId: string; // SimAccount.id
|
|
33
|
+
type: "text" | "card" | "image" | "file" | "raw_card" | "system";
|
|
34
|
+
content: string; // 文本内容或 JSON 字符串
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SimChat {
|
|
39
|
+
id: string; // "sim_default" | "sim_<uuid8>" | "p2p_alice_bot"
|
|
40
|
+
type: "group" | "p2p";
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
memberIds: string[]; // 所有成员,第一个是创建者
|
|
44
|
+
messages: SimMessage[]; // 消息历史(按时间排序)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** handleCommand 的回调签名 */
|
|
48
|
+
export type MessageHandler = (
|
|
49
|
+
text: string,
|
|
50
|
+
chatId: string,
|
|
51
|
+
openId: string,
|
|
52
|
+
timestamp: number,
|
|
53
|
+
chatType: string,
|
|
54
|
+
traceId?: string,
|
|
55
|
+
) => Promise<void>;
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// 持久化路径
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const SIM_DIR = join(homedir(), ".chatccc", "sim");
|
|
62
|
+
const MESSAGES_FILE = join(SIM_DIR, "messages.jsonl");
|
|
63
|
+
|
|
64
|
+
async function ensureDir(): Promise<void> {
|
|
65
|
+
await mkdir(SIM_DIR, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function appendJsonl(line: Record<string, unknown>): Promise<void> {
|
|
69
|
+
await ensureDir();
|
|
70
|
+
await appendFile(MESSAGES_FILE, JSON.stringify(line) + "\n", "utf-8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// SimStore
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export class SimStore extends EventEmitter {
|
|
78
|
+
accounts = new Map<string, SimAccount>();
|
|
79
|
+
chats = new Map<string, SimChat>();
|
|
80
|
+
|
|
81
|
+
// ---- 初始化 ----
|
|
82
|
+
|
|
83
|
+
constructor() {
|
|
84
|
+
super();
|
|
85
|
+
this._initDefaults();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private _initDefaults(): void {
|
|
89
|
+
// 注册 bot 账户
|
|
90
|
+
this.accounts.set("bot", { id: "bot", kind: "bot", name: "ChatCCC" });
|
|
91
|
+
|
|
92
|
+
// 注册默认用户
|
|
93
|
+
this.accounts.set("sim_user_001", { id: "sim_user_001", kind: "user", name: "Developer" });
|
|
94
|
+
|
|
95
|
+
// 创建默认群聊
|
|
96
|
+
const defaultChatId = "sim_default";
|
|
97
|
+
this.chats.set(defaultChatId, {
|
|
98
|
+
id: defaultChatId,
|
|
99
|
+
type: "group",
|
|
100
|
+
name: "默认模拟会话",
|
|
101
|
+
description: "",
|
|
102
|
+
memberIds: ["bot", "sim_user_001"],
|
|
103
|
+
messages: [],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- 账户管理 ----
|
|
108
|
+
|
|
109
|
+
registerAccount(account: SimAccount): void {
|
|
110
|
+
if (this.accounts.has(account.id)) {
|
|
111
|
+
throw new Error(`Account "${account.id}" already exists`);
|
|
112
|
+
}
|
|
113
|
+
this.accounts.set(account.id, account);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getAccount(id: string): SimAccount | undefined {
|
|
117
|
+
return this.accounts.get(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---- 会话管理 ----
|
|
121
|
+
|
|
122
|
+
createGroupChat(name: string, memberIds: string[]): SimChat {
|
|
123
|
+
const chatId = `sim_${randomUUID().slice(0, 8)}`;
|
|
124
|
+
// bot 始终是成员
|
|
125
|
+
const allMembers = ["bot", ...memberIds.filter((id) => id !== "bot")];
|
|
126
|
+
const chat: SimChat = {
|
|
127
|
+
id: chatId,
|
|
128
|
+
type: "group",
|
|
129
|
+
name,
|
|
130
|
+
description: "",
|
|
131
|
+
memberIds: allMembers,
|
|
132
|
+
messages: [],
|
|
133
|
+
};
|
|
134
|
+
this.chats.set(chatId, chat);
|
|
135
|
+
|
|
136
|
+
// 通知被拉入群的用户
|
|
137
|
+
for (const uid of memberIds) {
|
|
138
|
+
this.emit("member_added", { chatId, userId: uid });
|
|
139
|
+
}
|
|
140
|
+
this.emit("chat_created", { chat });
|
|
141
|
+
|
|
142
|
+
return chat;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
createP2pChat(userId: string): SimChat {
|
|
146
|
+
// p2p 命名规则:p2p_<userId>_bot
|
|
147
|
+
const chatId = `p2p_${userId}_bot`;
|
|
148
|
+
const existing = this.chats.get(chatId);
|
|
149
|
+
if (existing) return existing;
|
|
150
|
+
|
|
151
|
+
const user = this.accounts.get(userId);
|
|
152
|
+
const chat: SimChat = {
|
|
153
|
+
id: chatId,
|
|
154
|
+
type: "p2p",
|
|
155
|
+
name: user ? `${user.name} 的私聊` : `私聊`,
|
|
156
|
+
description: "",
|
|
157
|
+
memberIds: ["bot", userId],
|
|
158
|
+
messages: [],
|
|
159
|
+
};
|
|
160
|
+
this.chats.set(chatId, chat);
|
|
161
|
+
this.emit("chat_created", { chat });
|
|
162
|
+
return chat;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getChat(chatId: string): SimChat | undefined {
|
|
166
|
+
return this.chats.get(chatId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getChatsForUser(userId: string): SimChat[] {
|
|
170
|
+
return [...this.chats.values()].filter((c) => c.memberIds.includes(userId));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
addMember(chatId: string, userId: string): void {
|
|
174
|
+
const chat = this.chats.get(chatId);
|
|
175
|
+
if (!chat) throw new Error(`Chat not found: ${chatId}`);
|
|
176
|
+
if (!chat.memberIds.includes(userId)) {
|
|
177
|
+
chat.memberIds.push(userId);
|
|
178
|
+
this.emit("member_added", { chatId, userId });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
updateChatInfo(chatId: string, name: string, description: string): void {
|
|
183
|
+
const chat = this.chats.get(chatId);
|
|
184
|
+
if (chat) {
|
|
185
|
+
chat.name = name;
|
|
186
|
+
chat.description = description;
|
|
187
|
+
} else {
|
|
188
|
+
// 模拟模式下机器人可能尝试更新不存在的群(比如从旧状态恢复),兼容处理
|
|
189
|
+
this.chats.set(chatId, {
|
|
190
|
+
id: chatId,
|
|
191
|
+
type: "group",
|
|
192
|
+
name,
|
|
193
|
+
description,
|
|
194
|
+
memberIds: ["bot", "sim_user_001"],
|
|
195
|
+
messages: [],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
disbandChat(chatId: string): void {
|
|
201
|
+
this.chats.delete(chatId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---- 消息管理 ----
|
|
205
|
+
|
|
206
|
+
/** 任何参与者发送消息 */
|
|
207
|
+
recordMessage(
|
|
208
|
+
chatId: string,
|
|
209
|
+
senderId: string,
|
|
210
|
+
type: SimMessage["type"],
|
|
211
|
+
content: string,
|
|
212
|
+
): SimMessage {
|
|
213
|
+
const chat = this.chats.get(chatId);
|
|
214
|
+
if (!chat) throw new Error(`Chat not found: ${chatId}`);
|
|
215
|
+
|
|
216
|
+
const msg: SimMessage = {
|
|
217
|
+
id: randomUUID(),
|
|
218
|
+
chatId,
|
|
219
|
+
senderId,
|
|
220
|
+
type,
|
|
221
|
+
content,
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
};
|
|
224
|
+
chat.messages.push(msg);
|
|
225
|
+
|
|
226
|
+
// 写 JSONL
|
|
227
|
+
appendJsonl({
|
|
228
|
+
direction: senderId === "bot" ? "send" : "recv",
|
|
229
|
+
chat_id: chatId,
|
|
230
|
+
sender_id: senderId,
|
|
231
|
+
msg_type: type,
|
|
232
|
+
content,
|
|
233
|
+
timestamp: msg.timestamp,
|
|
234
|
+
}).catch(() => {});
|
|
235
|
+
|
|
236
|
+
// 通知所有订阅者
|
|
237
|
+
this.emit("message", { chatId, message: msg });
|
|
238
|
+
|
|
239
|
+
return msg;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** bot 发送回复——即使 chat 不存在也记录(兼容未知 chat) */
|
|
243
|
+
sendReply(chatId: string, type: SimMessage["type"], content: string): SimMessage {
|
|
244
|
+
const msg: SimMessage = {
|
|
245
|
+
id: randomUUID(),
|
|
246
|
+
chatId,
|
|
247
|
+
senderId: "bot",
|
|
248
|
+
type,
|
|
249
|
+
content,
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// 如果 chat 存在,追加到消息历史
|
|
254
|
+
const chat = this.chats.get(chatId);
|
|
255
|
+
if (chat) {
|
|
256
|
+
chat.messages.push(msg);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 写 JSONL
|
|
260
|
+
appendJsonl({
|
|
261
|
+
direction: "send",
|
|
262
|
+
chat_id: chatId,
|
|
263
|
+
msg_type: type,
|
|
264
|
+
content,
|
|
265
|
+
timestamp: msg.timestamp,
|
|
266
|
+
}).catch(() => {});
|
|
267
|
+
|
|
268
|
+
// 通知订阅者
|
|
269
|
+
this.emit("message", { chatId, message: msg });
|
|
270
|
+
|
|
271
|
+
return msg;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 获取指定会话的消息 */
|
|
275
|
+
getMessages(chatId: string, requesterId: string): SimMessage[] {
|
|
276
|
+
const chat = this.chats.get(chatId);
|
|
277
|
+
if (!chat) return [];
|
|
278
|
+
// 只有成员能看消息
|
|
279
|
+
if (!chat.memberIds.includes(requesterId)) return [];
|
|
280
|
+
return chat.messages;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** 重置所有状态(测试用) */
|
|
284
|
+
reset(): void {
|
|
285
|
+
this.accounts.clear();
|
|
286
|
+
this.chats.clear();
|
|
287
|
+
this.removeAllListeners();
|
|
288
|
+
this._initDefaults();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
// 单例 + 消息分发
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
export const simStore = new SimStore();
|
|
297
|
+
|
|
298
|
+
export const SIM_DEFAULT_CHAT_ID = "sim_default";
|
|
299
|
+
|
|
300
|
+
let _messageHandler: MessageHandler | null = null;
|
|
301
|
+
|
|
302
|
+
/** 注册 handleCommand 回调(由 index.ts 在模拟模式启动时调用) */
|
|
303
|
+
export function setMessageHandler(handler: MessageHandler): void {
|
|
304
|
+
_messageHandler = handler;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** 模拟用户发送消息 → 记录消息 + 触发 bot 处理 */
|
|
308
|
+
export async function dispatchMessage(
|
|
309
|
+
text: string,
|
|
310
|
+
chatId: string,
|
|
311
|
+
openId: string,
|
|
312
|
+
chatType: string,
|
|
313
|
+
): Promise<void> {
|
|
314
|
+
if (!_messageHandler) throw new Error("Message handler not registered — is simulate mode running?");
|
|
315
|
+
simStore.recordMessage(chatId, openId, "text", text);
|
|
316
|
+
await _messageHandler(text, chatId, openId, Date.now(), chatType);
|
|
313
317
|
}
|