@hchuanz/pocket-core 0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,3583 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AVA_BASE_URL: () => AVA_BASE_URL,
34
+ CallbackStreamSink: () => CallbackStreamSink,
35
+ ConsoleLogger: () => ConsoleLogger,
36
+ FlipCacheService: () => FlipCacheService,
37
+ GroupChatStore: () => GroupChatStore,
38
+ MIRROR_PRESETS: () => MIRROR_PRESETS,
39
+ MemoryConfigStore: () => MemoryConfigStore,
40
+ MemoryFlipDataSource: () => MemoryFlipDataSource,
41
+ MirrorIndex: () => MirrorIndex,
42
+ MirrorMemory: () => MirrorMemory,
43
+ MirrorStore: () => MirrorStore,
44
+ NodeJsonConfigStore: () => NodeJsonConfigStore,
45
+ NodeJsonFileStorage: () => NodeJsonFileStorage,
46
+ NodeJsonFlipDataSource: () => NodeJsonFlipDataSource,
47
+ NoopEventSink: () => NoopEventSink,
48
+ NoopLogger: () => NoopLogger,
49
+ OpenAICompatibleClient: () => OpenAICompatibleClient,
50
+ POCKET_API_BASE: () => POCKET_API_BASE,
51
+ POCKET_APP_INFO: () => POCKET_APP_INFO,
52
+ POCKET_PA: () => POCKET_PA,
53
+ POCKET_USER_AGENT: () => POCKET_USER_AGENT,
54
+ PRESET_MODELS: () => PRESET_MODELS,
55
+ PRESET_PROVIDERS: () => PRESET_PROVIDERS,
56
+ PlainTextSecureStorage: () => PlainTextSecureStorage,
57
+ PocketAuthExpiredError: () => PocketAuthExpiredError,
58
+ PocketClient: () => PocketClient,
59
+ SESSION_EXPIRE_MS: () => SESSION_EXPIRE_MS,
60
+ ToolRegistry: () => ToolRegistry,
61
+ agentLoop: () => agentLoop,
62
+ agentLoopStream: () => agentLoopStream,
63
+ buildInnerMonologuePrompt: () => buildInnerMonologuePrompt,
64
+ buildMirror: () => buildMirror,
65
+ buildSpeakPrompt: () => buildSpeakPrompt,
66
+ chat: () => chat,
67
+ clearChatMemory: () => clearChatMemory,
68
+ clearMirrorMemory: () => clearMirrorMemory,
69
+ createMirrorTools: () => createMirrorTools,
70
+ createPlatformTools: () => createPlatformTools,
71
+ createPocketCore: () => createPocketCore,
72
+ createPocketTools: () => createPocketTools,
73
+ defaultModelConfig: () => defaultModelConfig,
74
+ fetchMemberList: () => fetchMemberList,
75
+ getAllFlips: () => getAllFlips,
76
+ getDataSourcePage: () => getDataSourcePage,
77
+ getImUserInfo: () => getImUserInfo,
78
+ getMemberFlipHistory: () => getMemberFlipHistory,
79
+ getMemberFlipPriceInfo: () => getMemberFlipPriceInfo,
80
+ getMemberListByTab: () => getMemberListByTab,
81
+ getMirrorConfig: () => getMirrorConfig,
82
+ getRoomChannelByStar: () => getRoomChannelByStar,
83
+ getRoomInfo: () => getRoomInfo,
84
+ getRoomMessages: () => getRoomMessages,
85
+ getTeamListInfo: () => getTeamListInfo,
86
+ getUnreadMessageNum: () => getUnreadMessageNum,
87
+ loginWithCode: () => loginWithCode,
88
+ loginWithToken: () => loginWithToken,
89
+ normalizePersona: () => normalizePersona,
90
+ normalizePocketUser: () => normalizePocketUser,
91
+ rollbackPersona: () => rollbackPersona,
92
+ runRound: () => runRound,
93
+ saveAssistantMessage: () => saveAssistantMessage,
94
+ sendFlip: () => sendFlip,
95
+ sendVerificationCode: () => sendVerificationCode,
96
+ shouldTriggerGrowth: () => shouldTriggerGrowth,
97
+ syncFlips: () => syncFlips,
98
+ triggerGrowth: () => triggerGrowth
99
+ });
100
+ module.exports = __toCommonJS(index_exports);
101
+
102
+ // src/constants/pocket.ts
103
+ var POCKET_API_BASE = "https://pocketapi.48.cn";
104
+ var AVA_BASE_URL = "https://source.48.cn";
105
+ var POCKET_APP_INFO = JSON.stringify({
106
+ vendor: "apple",
107
+ deviceId: "2A01B81B-653A-43EC-9C58-95CB418A58AC",
108
+ appVersion: "7.1.37",
109
+ appBuild: "26020801",
110
+ osVersion: "26.4.0",
111
+ osType: "ios",
112
+ deviceName: "iPhone17,1",
113
+ os: "ios"
114
+ });
115
+ var POCKET_PA = "MTc4MjM3NDEwNjAwMCw3NzE1LEIwODM5QzdEMjZFQzJFOTFERTExNDVERDU2NTlBRjk5LA==";
116
+ var POCKET_USER_AGENT = "PocketFans201807/7.1.37 (iPhone; iOS 26.4; Scale/3.00)";
117
+ var SESSION_EXPIRE_MS = 30 * 24 * 60 * 60 * 1e3;
118
+
119
+ // src/adapters/loggers.ts
120
+ var ConsoleLogger = class {
121
+ debug(...args) {
122
+ console.debug(...args);
123
+ }
124
+ info(...args) {
125
+ console.info(...args);
126
+ }
127
+ warn(...args) {
128
+ console.warn(...args);
129
+ }
130
+ error(...args) {
131
+ console.error(...args);
132
+ }
133
+ };
134
+ var NoopLogger = class {
135
+ debug() {
136
+ }
137
+ info() {
138
+ }
139
+ warn() {
140
+ }
141
+ error() {
142
+ }
143
+ };
144
+
145
+ // src/adapters/node-json-storage.ts
146
+ var import_node_fs = __toESM(require("fs"), 1);
147
+ var import_node_path = __toESM(require("path"), 1);
148
+ var NodeJsonFileStorage = class {
149
+ root;
150
+ constructor(root) {
151
+ this.root = root;
152
+ }
153
+ resolve(subpath) {
154
+ return import_node_path.default.join(this.root, subpath);
155
+ }
156
+ readJson(subpath) {
157
+ const fp = this.resolve(subpath);
158
+ if (!import_node_fs.default.existsSync(fp)) return null;
159
+ try {
160
+ return JSON.parse(import_node_fs.default.readFileSync(fp, "utf-8"));
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+ writeJson(subpath, data) {
166
+ const fp = this.resolve(subpath);
167
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(fp), { recursive: true });
168
+ import_node_fs.default.writeFileSync(fp, JSON.stringify(data, null, 2), "utf-8");
169
+ }
170
+ exists(subpath) {
171
+ return import_node_fs.default.existsSync(this.resolve(subpath));
172
+ }
173
+ delete(subpath) {
174
+ const fp = this.resolve(subpath);
175
+ if (import_node_fs.default.existsSync(fp)) import_node_fs.default.unlinkSync(fp);
176
+ }
177
+ list(dirSubpath) {
178
+ const dir = this.resolve(dirSubpath);
179
+ if (!import_node_fs.default.existsSync(dir)) return [];
180
+ return import_node_fs.default.readdirSync(dir);
181
+ }
182
+ mkdir(dirSubpath) {
183
+ import_node_fs.default.mkdirSync(this.resolve(dirSubpath), { recursive: true });
184
+ }
185
+ };
186
+
187
+ // src/adapters/plain-text-secure-storage.ts
188
+ var PlainTextSecureStorage = class {
189
+ isAvailable() {
190
+ return true;
191
+ }
192
+ encryptString(plain) {
193
+ return Buffer.from(plain, "utf-8").toString("base64");
194
+ }
195
+ decryptString(encoded) {
196
+ return Buffer.from(encoded, "base64").toString("utf-8");
197
+ }
198
+ };
199
+
200
+ // src/adapters/node-json-config-store.ts
201
+ var import_node_fs2 = __toESM(require("fs"), 1);
202
+ var import_node_path2 = __toESM(require("path"), 1);
203
+ var NodeJsonConfigStore = class {
204
+ filePath;
205
+ data = {};
206
+ loaded = false;
207
+ constructor(root, filename = "app-store.json") {
208
+ this.filePath = import_node_path2.default.join(root, filename);
209
+ }
210
+ ensureLoaded() {
211
+ if (this.loaded) return;
212
+ this.loaded = true;
213
+ try {
214
+ if (import_node_fs2.default.existsSync(this.filePath)) {
215
+ this.data = JSON.parse(import_node_fs2.default.readFileSync(this.filePath, "utf-8"));
216
+ }
217
+ } catch {
218
+ this.data = {};
219
+ }
220
+ }
221
+ save() {
222
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(this.filePath), { recursive: true });
223
+ import_node_fs2.default.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
224
+ }
225
+ get(key) {
226
+ this.ensureLoaded();
227
+ return this.data[key];
228
+ }
229
+ set(key, value) {
230
+ this.ensureLoaded();
231
+ if (value === void 0 || value === null) {
232
+ delete this.data[key];
233
+ } else {
234
+ this.data[key] = value;
235
+ }
236
+ this.save();
237
+ }
238
+ remove(key) {
239
+ this.ensureLoaded();
240
+ delete this.data[key];
241
+ this.save();
242
+ }
243
+ has(key) {
244
+ this.ensureLoaded();
245
+ return key in this.data;
246
+ }
247
+ clear() {
248
+ this.data = {};
249
+ this.save();
250
+ }
251
+ };
252
+
253
+ // src/adapters/memory-config-store.ts
254
+ var MemoryConfigStore = class {
255
+ data = /* @__PURE__ */ new Map();
256
+ get(key) {
257
+ return this.data.get(key);
258
+ }
259
+ set(key, value) {
260
+ this.data.set(key, value);
261
+ }
262
+ remove(key) {
263
+ this.data.delete(key);
264
+ }
265
+ has(key) {
266
+ return this.data.has(key);
267
+ }
268
+ clear() {
269
+ this.data.clear();
270
+ }
271
+ };
272
+
273
+ // src/adapters/node-json-flip-source.ts
274
+ var import_node_path3 = __toESM(require("path"), 1);
275
+ var CACHE_DIR = "flip-cache";
276
+ var NodeJsonFlipDataSource = class {
277
+ storage;
278
+ readLegacy;
279
+ constructor(storage, readLegacy) {
280
+ this.storage = storage;
281
+ this.readLegacy = readLegacy;
282
+ }
283
+ filePath(userId) {
284
+ return import_node_path3.default.join(CACHE_DIR, `${userId}.json`);
285
+ }
286
+ get(userId) {
287
+ if (!userId) return null;
288
+ const cached = this.storage.readJson(this.filePath(userId));
289
+ if (cached) return cached;
290
+ return this.migrateFromLegacy(userId);
291
+ }
292
+ set(cache) {
293
+ this.storage.writeJson(this.filePath(cache.userId), cache);
294
+ }
295
+ clear(userId) {
296
+ this.storage.delete(this.filePath(userId));
297
+ }
298
+ migrateFromLegacy(userId) {
299
+ if (!this.readLegacy) return null;
300
+ const legacy = this.readLegacy();
301
+ const records = legacy?.allUserData?.[userId];
302
+ if (!Array.isArray(records) || records.length === 0) return null;
303
+ const cache = {
304
+ userId,
305
+ records,
306
+ groupedDataById: legacy?.groupedDataById || {},
307
+ dashboardInfo: legacy?.dashboardInfo || {
308
+ totalFlipCount: records.length,
309
+ runningCount: 0,
310
+ returnedCount: 0,
311
+ costTotal: 0
312
+ },
313
+ meta: { syncedAt: Date.now(), recordCount: records.length }
314
+ };
315
+ this.set(cache);
316
+ return cache;
317
+ }
318
+ };
319
+
320
+ // src/adapters/memory-flip-source.ts
321
+ var MemoryFlipDataSource = class {
322
+ data = /* @__PURE__ */ new Map();
323
+ get(userId) {
324
+ return this.data.get(userId) ?? null;
325
+ }
326
+ set(cache) {
327
+ this.data.set(cache.userId, cache);
328
+ }
329
+ clear(userId) {
330
+ this.data.delete(userId);
331
+ }
332
+ };
333
+
334
+ // src/adapters/openai-compatible-client.ts
335
+ var OpenAICompatibleClient = class {
336
+ logger;
337
+ constructor(logger) {
338
+ this.logger = logger;
339
+ }
340
+ async chatCompletion(config, messages, tools, options) {
341
+ const url = `${config.baseUrl}/chat/completions`;
342
+ const body = {
343
+ model: options?.model || config.chatModel,
344
+ messages,
345
+ stream: false
346
+ };
347
+ if (tools?.length) {
348
+ body.tools = tools;
349
+ body.tool_choice = "auto";
350
+ }
351
+ if (options?.maxTokens) body.max_tokens = options.maxTokens;
352
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
353
+ const resp = await fetch(url, {
354
+ method: "POST",
355
+ headers: {
356
+ "Content-Type": "application/json",
357
+ Authorization: `Bearer ${config.apiKey}`
358
+ },
359
+ body: JSON.stringify(body),
360
+ signal: AbortSignal.timeout(options?.timeoutMs ?? 12e4)
361
+ });
362
+ if (!resp.ok) {
363
+ const text = await resp.text().catch(() => "");
364
+ throw new Error(`LLM ${resp.status}: ${text.slice(0, 200)}`);
365
+ }
366
+ const json = await resp.json();
367
+ const choice = json.choices?.[0];
368
+ return {
369
+ content: choice?.message?.content ?? "",
370
+ toolCalls: choice?.message?.tool_calls
371
+ };
372
+ }
373
+ async chatCompletionStream(config, messages, onChunk, tools, options) {
374
+ const url = `${config.baseUrl}/chat/completions`;
375
+ const body = {
376
+ model: options?.model || config.chatModel,
377
+ messages,
378
+ stream: true,
379
+ stream_options: { include_usage: true }
380
+ };
381
+ if (tools?.length) {
382
+ body.tools = tools;
383
+ body.tool_choice = "auto";
384
+ }
385
+ if (options?.maxTokens) body.max_tokens = options.maxTokens;
386
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
387
+ const resp = await fetch(url, {
388
+ method: "POST",
389
+ headers: {
390
+ "Content-Type": "application/json",
391
+ Authorization: `Bearer ${config.apiKey}`
392
+ },
393
+ body: JSON.stringify(body),
394
+ signal: AbortSignal.timeout(options?.timeoutMs ?? 12e4)
395
+ });
396
+ if (!resp.ok) {
397
+ const text = await resp.text().catch(() => "");
398
+ throw new Error(`LLM stream ${resp.status}: ${text.slice(0, 200)}`);
399
+ }
400
+ const reader = resp.body?.getReader();
401
+ if (!reader) throw new Error("No response body");
402
+ const decoder = new TextDecoder();
403
+ let buffer = "";
404
+ const toolCallMap = /* @__PURE__ */ new Map();
405
+ try {
406
+ while (true) {
407
+ const { done, value } = await reader.read();
408
+ if (done) break;
409
+ buffer += decoder.decode(value, { stream: true });
410
+ const lines = buffer.split("\n");
411
+ buffer = lines.pop() ?? "";
412
+ for (const line of lines) {
413
+ if (!line.startsWith("data: ")) continue;
414
+ const data = line.slice(6).trim();
415
+ if (data === "[DONE]") break;
416
+ try {
417
+ const json = JSON.parse(data);
418
+ const delta = json.choices?.[0]?.delta;
419
+ if (!delta) continue;
420
+ const content = delta.content ?? "";
421
+ for (const tc of delta.tool_calls ?? []) {
422
+ const idx = tc.index ?? 0;
423
+ const existing = toolCallMap.get(idx) ?? { id: "", name: "", arguments: "" };
424
+ if (tc.id) existing.id = tc.id;
425
+ if (tc.function?.name) existing.name += tc.function.name;
426
+ if (tc.function?.arguments) existing.arguments += tc.function.arguments;
427
+ toolCallMap.set(idx, existing);
428
+ }
429
+ onChunk({
430
+ content,
431
+ toolCalls: Array.from(toolCallMap.entries()).map(([index, tc]) => ({ index, ...tc })),
432
+ isDone: false
433
+ });
434
+ } catch {
435
+ }
436
+ }
437
+ }
438
+ } finally {
439
+ reader.releaseLock();
440
+ }
441
+ const finalToolCalls = toolCallMap.size > 0 ? Array.from(toolCallMap.values()).map((tc) => ({
442
+ id: tc.id,
443
+ type: "function",
444
+ function: { name: tc.name, arguments: tc.arguments }
445
+ })) : void 0;
446
+ onChunk({ content: "", toolCalls: [], isDone: true });
447
+ return finalToolCalls;
448
+ }
449
+ async embed(config, texts, model) {
450
+ const url = `${config.baseUrl}/embeddings`;
451
+ const resp = await fetch(url, {
452
+ method: "POST",
453
+ headers: {
454
+ "Content-Type": "application/json",
455
+ Authorization: `Bearer ${config.apiKey}`
456
+ },
457
+ body: JSON.stringify({
458
+ model: model || config.embeddingModel,
459
+ input: texts,
460
+ encoding_format: "float"
461
+ }),
462
+ signal: AbortSignal.timeout(6e4)
463
+ });
464
+ if (!resp.ok) {
465
+ const text = await resp.text().catch(() => "");
466
+ throw new Error(`Embedding ${resp.status}: ${text.slice(0, 200)}`);
467
+ }
468
+ const json = await resp.json();
469
+ return (json.data ?? []).map((d) => d.embedding);
470
+ }
471
+ async testConnection(config) {
472
+ try {
473
+ const result = await this.chatCompletion(
474
+ config,
475
+ [{ role: "user", content: "ping" }],
476
+ void 0,
477
+ { maxTokens: 10, timeoutMs: 15e3 }
478
+ );
479
+ return {
480
+ ok: result.content.length > 0 || result.content.length === 0 && !result.toolCalls,
481
+ message: result.content.slice(0, 100) || "(empty response)"
482
+ };
483
+ } catch (e) {
484
+ this.logger.warn("[OpenAICompatibleClient] testConnection failed:", e);
485
+ return {
486
+ ok: false,
487
+ message: e instanceof Error ? e.message : String(e)
488
+ };
489
+ }
490
+ }
491
+ };
492
+
493
+ // src/adapters/callback-stream-sink.ts
494
+ var CallbackStreamSink = class {
495
+ onChunkCb;
496
+ constructor(cb) {
497
+ this.onChunkCb = cb;
498
+ }
499
+ onChunk(content, isDone, newSessionId) {
500
+ this.onChunkCb(content, isDone, newSessionId);
501
+ }
502
+ };
503
+ var NoopEventSink = class {
504
+ emit() {
505
+ }
506
+ };
507
+
508
+ // src/tools/tool-registry.ts
509
+ var ToolRegistry = class {
510
+ constructor(logger) {
511
+ this.logger = logger;
512
+ }
513
+ logger;
514
+ tools = /* @__PURE__ */ new Map();
515
+ /**
516
+ * Register a tool (platform / agent / user MCP)
517
+ */
518
+ register(tool) {
519
+ if (this.tools.has(tool.function.name)) {
520
+ this.logger.warn(`[toolRegistry] overwriting tool: ${tool.function.name}`);
521
+ }
522
+ this.tools.set(tool.function.name, tool);
523
+ this.logger.info(`[toolRegistry] registered: ${tool.function.name}`);
524
+ }
525
+ /**
526
+ * Bulk register tools from an array
527
+ */
528
+ registerAll(tools) {
529
+ for (const t of tools) this.register(t);
530
+ }
531
+ /**
532
+ * Unregister a tool by name
533
+ */
534
+ unregister(name) {
535
+ this.tools.delete(name);
536
+ }
537
+ /**
538
+ * Unregister all tools with a given prefix (e.g. MCP server name)
539
+ */
540
+ unregisterByPrefix(prefix) {
541
+ for (const name of this.tools.keys()) {
542
+ if (name.startsWith(prefix)) this.tools.delete(name);
543
+ }
544
+ }
545
+ /**
546
+ * Get a single tool by name
547
+ */
548
+ get(name) {
549
+ return this.tools.get(name);
550
+ }
551
+ /**
552
+ * Get all tools in OpenAI function-calling format
553
+ */
554
+ getOpenAIFormat() {
555
+ return Array.from(this.tools.values()).map((t) => ({
556
+ type: "function",
557
+ function: { ...t.function }
558
+ }));
559
+ }
560
+ /**
561
+ * Get tools filtered by category
562
+ */
563
+ getByCategory(category) {
564
+ return Array.from(this.tools.values()).filter((t) => t.category === category).map((t) => ({ type: "function", function: { ...t.function } }));
565
+ }
566
+ /**
567
+ * Execute a tool by name
568
+ */
569
+ async execute(name, args, context) {
570
+ const tool = this.tools.get(name);
571
+ if (!tool) throw new Error(`Tool not found: ${name}`);
572
+ return tool.handler(args, context);
573
+ }
574
+ /**
575
+ * List all registered tool names
576
+ */
577
+ listNames() {
578
+ return Array.from(this.tools.keys());
579
+ }
580
+ /**
581
+ * Clear all tools
582
+ */
583
+ clear() {
584
+ this.tools.clear();
585
+ }
586
+ };
587
+
588
+ // src/types/mirror.ts
589
+ function normalizePersona(persona) {
590
+ const p = persona || {};
591
+ const rel = p.relationship;
592
+ return {
593
+ personalityTraits: Array.isArray(p.personalityTraits) ? p.personalityTraits : [],
594
+ speechPatterns: Array.isArray(p.speechPatterns) ? p.speechPatterns : [],
595
+ signaturePhrases: Array.isArray(p.signaturePhrases) ? p.signaturePhrases : [],
596
+ emotionalTone: typeof p.emotionalTone === "string" ? p.emotionalTone : "\u6E29\u67D4\u771F\u8BDA",
597
+ personaSummary: typeof p.personaSummary === "string" ? p.personaSummary : "",
598
+ ...rel ? {
599
+ relationship: {
600
+ dynamic: typeof rel.dynamic === "string" ? rel.dynamic : "\u53CB\u597D",
601
+ closeness: typeof rel.closeness === "number" ? rel.closeness : 0.5,
602
+ howSheAddressesMe: typeof rel.howSheAddressesMe === "string" ? rel.howSheAddressesMe : "\u4F60",
603
+ topicsWeDiscuss: Array.isArray(rel.topicsWeDiscuss) ? rel.topicsWeDiscuss : [],
604
+ evolution: typeof rel.evolution === "string" ? rel.evolution : ""
605
+ }
606
+ } : {},
607
+ ...p.recentActivity ? {
608
+ recentActivity: {
609
+ topics: Array.isArray(p.recentActivity.topics) ? p.recentActivity.topics : [],
610
+ mood: typeof p.recentActivity.mood === "string" ? p.recentActivity.mood : "",
611
+ events: Array.isArray(p.recentActivity.events) ? p.recentActivity.events : []
612
+ }
613
+ } : {}
614
+ };
615
+ }
616
+
617
+ // src/mirror/mirror-store.ts
618
+ var MIRROR_DIR = "mirrors";
619
+ var CHAT_DIR = "chat_sessions";
620
+ function mirrorSubdir(userId) {
621
+ return `${MIRROR_DIR}/${userId}`;
622
+ }
623
+ function chatSubdir(userId) {
624
+ return `${CHAT_DIR}/${userId}`;
625
+ }
626
+ function profileSubpath(xoxId, userId) {
627
+ return `${MIRROR_DIR}/${userId}/${xoxId}.json`;
628
+ }
629
+ function sessionSubpath(sessionId, userId) {
630
+ return `${CHAT_DIR}/${userId}/${sessionId}.json`;
631
+ }
632
+ function normalizeProfile(raw) {
633
+ return {
634
+ ...raw,
635
+ persona: normalizePersona(raw.persona),
636
+ previousPersona: raw.previousPersona ? normalizePersona(raw.previousPersona) : void 0,
637
+ fewShotExamples: Array.isArray(raw.fewShotExamples) ? raw.fewShotExamples : [],
638
+ meta: raw.meta || { totalSamplesUsed: 0, lastUpdatedAt: 0, dataRangeMs: [0, 0], maturityScore: 0 }
639
+ };
640
+ }
641
+ var MirrorStore = class {
642
+ constructor(storage, logger) {
643
+ this.storage = storage;
644
+ this.logger = logger;
645
+ }
646
+ storage;
647
+ logger;
648
+ // ═══ Mirror Profile CRUD ═══
649
+ getProfile(xoxId, userId) {
650
+ const raw = this.storage.readJson(profileSubpath(xoxId, userId));
651
+ if (!raw) return null;
652
+ try {
653
+ return normalizeProfile(raw);
654
+ } catch (e) {
655
+ this.logger.warn(`[mirrorStore] read profile failed for ${xoxId}`, e);
656
+ return null;
657
+ }
658
+ }
659
+ saveProfile(profile, userId) {
660
+ this.storage.mkdir(mirrorSubdir(userId));
661
+ this.storage.writeJson(profileSubpath(profile.xoxId, userId), profile);
662
+ this.logger.info(`[mirrorStore] saved profile for ${profile.xoxNickname}(${profile.xoxId})`);
663
+ }
664
+ deleteProfile(xoxId, userId) {
665
+ this.storage.delete(profileSubpath(xoxId, userId));
666
+ const cd = chatSubdir(userId);
667
+ if (this.storage.exists(cd)) {
668
+ const files = this.storage.list(cd);
669
+ for (const f of files) {
670
+ try {
671
+ const session = this.storage.readJson(`${cd}/${f}`);
672
+ if (session?.xoxId === xoxId) {
673
+ this.storage.delete(`${cd}/${f}`);
674
+ }
675
+ } catch {
676
+ }
677
+ }
678
+ }
679
+ }
680
+ listProfiles(userId) {
681
+ const dir = mirrorSubdir(userId);
682
+ if (!this.storage.exists(dir)) return [];
683
+ const profiles = [];
684
+ const files = this.storage.list(dir);
685
+ for (const f of files) {
686
+ if (!f.endsWith(".json")) continue;
687
+ if (f.includes("_index")) continue;
688
+ if (f.includes("_memory")) continue;
689
+ try {
690
+ const profile = this.storage.readJson(`${dir}/${f}`);
691
+ if (profile) profiles.push(normalizeProfile(profile));
692
+ } catch {
693
+ }
694
+ }
695
+ return profiles;
696
+ }
697
+ // ═══ Chat Session CRUD ═══
698
+ getSession(sessionId, userId) {
699
+ return this.storage.readJson(sessionSubpath(sessionId, userId));
700
+ }
701
+ saveSession(session, userId) {
702
+ this.storage.mkdir(chatSubdir(userId));
703
+ this.storage.writeJson(sessionSubpath(session.sessionId, userId), session);
704
+ }
705
+ /**
706
+ * Find the most recently active chat session for an idol
707
+ */
708
+ getLatestSessionForXox(xoxId, userId) {
709
+ const cd = chatSubdir(userId);
710
+ if (!this.storage.exists(cd)) return null;
711
+ let latest = null;
712
+ const files = this.storage.list(cd);
713
+ for (const f of files) {
714
+ if (!f.endsWith(".json")) continue;
715
+ try {
716
+ const session = this.storage.readJson(`${cd}/${f}`);
717
+ if (session && session.xoxId === xoxId) {
718
+ if (!latest || session.lastActiveAt > latest.lastActiveAt) {
719
+ latest = session;
720
+ }
721
+ }
722
+ } catch {
723
+ }
724
+ }
725
+ return latest;
726
+ }
727
+ deleteSession(sessionId, userId) {
728
+ this.storage.delete(sessionSubpath(sessionId, userId));
729
+ }
730
+ /**
731
+ * Delete all chat sessions for an idol (keeps profile)
732
+ */
733
+ deleteSessionsForXox(xoxId, userId) {
734
+ const cd = chatSubdir(userId);
735
+ if (!this.storage.exists(cd)) return;
736
+ const files = this.storage.list(cd);
737
+ for (const f of files) {
738
+ try {
739
+ const session = this.storage.readJson(`${cd}/${f}`);
740
+ if (session?.xoxId === xoxId) {
741
+ this.storage.delete(`${cd}/${f}`);
742
+ }
743
+ } catch {
744
+ }
745
+ }
746
+ }
747
+ };
748
+
749
+ // src/mirror/mirror-index.ts
750
+ var import_node_crypto = __toESM(require("crypto"), 1);
751
+ var INDEX_DIR = "mirrors";
752
+ var EMBED_BATCH_SIZE = 32;
753
+ function indexPath(xoxId) {
754
+ return `${INDEX_DIR}/${xoxId}_index.json`;
755
+ }
756
+ function hashRecord(content, answerContent) {
757
+ return import_node_crypto.default.createHash("md5").update(`${content}||${answerContent}`).digest("hex").slice(0, 12);
758
+ }
759
+ function cosine(a, b) {
760
+ let dot = 0;
761
+ let na = 0;
762
+ let nb = 0;
763
+ for (let i = 0; i < a.length; i++) {
764
+ dot += a[i] * b[i];
765
+ na += a[i] * a[i];
766
+ nb += b[i] * b[i];
767
+ }
768
+ if (na === 0 || nb === 0) return 0;
769
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
770
+ }
771
+ var MirrorIndex = class {
772
+ constructor(storage, logger, llmClient) {
773
+ this.storage = storage;
774
+ this.logger = logger;
775
+ this.llmClient = llmClient;
776
+ }
777
+ storage;
778
+ logger;
779
+ llmClient;
780
+ /**
781
+ * Load index from disk (returns null if not exists)
782
+ */
783
+ load(xoxId) {
784
+ const fp = indexPath(xoxId);
785
+ const raw = this.storage.readJson(fp);
786
+ if (!raw) return null;
787
+ return raw;
788
+ }
789
+ /**
790
+ * Build / incrementally update the vector index for an idol's flip records.
791
+ * Records with unchanged hash reuse existing embeddings.
792
+ */
793
+ async buildIndex(xoxId, config, records) {
794
+ const existing = this.load(xoxId);
795
+ const existingMap = /* @__PURE__ */ new Map();
796
+ if (existing && existing.model === config.embeddingModel) {
797
+ for (const v of existing.vectors) existingMap.set(v.hash, v);
798
+ }
799
+ const toEmbed = [];
800
+ const reused = [];
801
+ for (const r of records) {
802
+ const content = String(r.content || "").trim();
803
+ const answer = String(r.answerContent || "").trim();
804
+ if (!content && !answer) continue;
805
+ const hash = hashRecord(content, answer);
806
+ const cached = existingMap.get(hash);
807
+ if (cached) {
808
+ reused.push(cached);
809
+ } else {
810
+ toEmbed.push({
811
+ rec: {
812
+ answerId: String(r.answerId || ""),
813
+ content,
814
+ answerContent: answer,
815
+ qtime: Number(r.qtime) || 0
816
+ },
817
+ hash,
818
+ text: `\u95EE\uFF1A${content} \u7B54\uFF1A${answer}`
819
+ });
820
+ }
821
+ }
822
+ const newVectors = [];
823
+ for (let i = 0; i < toEmbed.length; i += EMBED_BATCH_SIZE) {
824
+ const batch = toEmbed.slice(i, i + EMBED_BATCH_SIZE);
825
+ const texts = batch.map((b) => b.text);
826
+ const vectors = await this.llmClient.embed(config, texts);
827
+ batch.forEach((b, j) => {
828
+ newVectors.push({ ...b.rec, hash: b.hash, vector: vectors[j] || [] });
829
+ });
830
+ }
831
+ const indexFile = {
832
+ xoxId,
833
+ model: config.embeddingModel,
834
+ updatedAt: Date.now(),
835
+ vectors: [...reused, ...newVectors]
836
+ };
837
+ this.storage.mkdir(INDEX_DIR);
838
+ this.storage.writeJson(indexPath(xoxId), indexFile);
839
+ this.logger.info(`[mirrorIndex] built index for ${xoxId}: ${newVectors.length} new, ${reused.length} reused`);
840
+ return { indexed: newVectors.length, reused: reused.length };
841
+ }
842
+ /**
843
+ * Delete index for an idol
844
+ */
845
+ deleteIndex(xoxId) {
846
+ this.storage.delete(indexPath(xoxId));
847
+ }
848
+ /**
849
+ * Search with cosine top-K + MMR dedup
850
+ */
851
+ async search(xoxId, config, query, topK = 5) {
852
+ const index = this.load(xoxId);
853
+ if (!index || index.vectors.length === 0) return [];
854
+ const [qv] = await this.llmClient.embed(config, [query]);
855
+ if (!qv) return [];
856
+ const scored = index.vectors.map((v) => ({ record: v, score: cosine(qv, v.vector) })).sort((a, b) => b.score - a.score).slice(0, Math.min(topK * 3, index.vectors.length));
857
+ const selected = [];
858
+ const LAMBDA = 0.75;
859
+ const pool = [...scored];
860
+ while (selected.length < topK && pool.length > 0) {
861
+ let best = null;
862
+ let bestScore = -Infinity;
863
+ for (const cand of pool) {
864
+ let maxSim = 0;
865
+ for (const sel of selected) {
866
+ const sim = cosine(cand.record.vector, sel.record.vector);
867
+ if (sim > maxSim) maxSim = sim;
868
+ }
869
+ const mmr = LAMBDA * cand.score - (1 - LAMBDA) * maxSim;
870
+ if (mmr > bestScore) {
871
+ bestScore = mmr;
872
+ best = cand;
873
+ }
874
+ }
875
+ if (!best) break;
876
+ selected.push(best);
877
+ pool.splice(pool.indexOf(best), 1);
878
+ }
879
+ return selected;
880
+ }
881
+ /**
882
+ * Get index stats
883
+ */
884
+ getStats(xoxId) {
885
+ const index = this.load(xoxId);
886
+ if (!index) return null;
887
+ return { total: index.vectors.length, updatedAt: index.updatedAt };
888
+ }
889
+ };
890
+
891
+ // src/mirror/mirror-memory.ts
892
+ var MEMORY_DIR = "mirrors";
893
+ var MAX_EPISODES = 50;
894
+ function memoryPath(xoxId) {
895
+ return `${MEMORY_DIR}/${xoxId}_memory.json`;
896
+ }
897
+ var MirrorMemory = class {
898
+ constructor(storage, logger, llmClient) {
899
+ this.storage = storage;
900
+ this.logger = logger;
901
+ this.llmClient = llmClient;
902
+ }
903
+ storage;
904
+ logger;
905
+ llmClient;
906
+ ensureDir() {
907
+ if (!this.storage.exists(MEMORY_DIR)) {
908
+ this.storage.mkdir(MEMORY_DIR);
909
+ }
910
+ }
911
+ /**
912
+ * Load memory file
913
+ */
914
+ load(xoxId) {
915
+ const mp = memoryPath(xoxId);
916
+ const raw = this.storage.readJson(mp);
917
+ if (raw) return raw;
918
+ return { xoxId, totalTurns: 0, episodes: [], updatedAt: Date.now() };
919
+ }
920
+ save(mem) {
921
+ this.ensureDir();
922
+ mem.updatedAt = Date.now();
923
+ this.storage.writeJson(memoryPath(mem.xoxId), mem);
924
+ }
925
+ /**
926
+ * Increment turn counter (called after each chat round)
927
+ */
928
+ addTurn(xoxId) {
929
+ const mem = this.load(xoxId);
930
+ mem.totalTurns += 1;
931
+ this.save(mem);
932
+ return mem.totalTurns;
933
+ }
934
+ getTotalTurns(xoxId) {
935
+ return this.load(xoxId).totalTurns;
936
+ }
937
+ /**
938
+ * Summarize a batch of conversation messages into an episodic memory entry.
939
+ * Uses LLM to extract topic + summary.
940
+ */
941
+ async recordEpisode(xoxId, config, messages) {
942
+ if (messages.length < 4) return null;
943
+ const convText = messages.slice(-30).map((m) => `${m.role === "user" ? "\u7C89\u4E1D" : "\u5076\u50CF"}: ${m.content}`).join("\n");
944
+ const systemPrompt = `\u4F60\u662F\u5BF9\u8BDD\u8BB0\u5FC6\u538B\u7F29\u5668\u3002\u8BF7\u628A\u4EE5\u4E0B\u5BF9\u8BDD\u538B\u7F29\u6210\u4E00\u6761\u8BB0\u5FC6\u6761\u76EE\uFF0C\u8F93\u51FA JSON:
945
+ {"topic": "\u8BDD\u9898\u5173\u952E\u8BCD(2-6\u5B57)", "summary": "30-80\u5B57\u7684\u5BF9\u8BDD\u6458\u8981\uFF0C\u5305\u542B\u5173\u952E\u4E8B\u5B9E\u3001\u60C5\u7EEA\u548C\u7EA6\u5B9A"}
946
+ \u53EA\u8F93\u51FA JSON\uFF0C\u4E0D\u8981\u5176\u4ED6\u5185\u5BB9\u3002`;
947
+ try {
948
+ const resp = await this.llmClient.chatCompletion(
949
+ config,
950
+ [
951
+ { role: "system", content: systemPrompt },
952
+ { role: "user", content: convText }
953
+ ],
954
+ void 0,
955
+ { model: config.lightweightModel }
956
+ // use lightweight model for summarization
957
+ );
958
+ const jsonMatch = resp.content.match(/\{[\s\S]*\}/);
959
+ if (!jsonMatch) return null;
960
+ const parsed = JSON.parse(jsonMatch[0]);
961
+ const entry = {
962
+ id: `${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
963
+ topic: parsed.topic || "\u672A\u5206\u7C7B",
964
+ summary: parsed.summary || "",
965
+ turns: Math.floor(messages.length / 2),
966
+ createdAt: Date.now()
967
+ };
968
+ const mem = this.load(xoxId);
969
+ mem.episodes.push(entry);
970
+ if (mem.episodes.length > MAX_EPISODES) {
971
+ mem.episodes = mem.episodes.slice(-MAX_EPISODES);
972
+ }
973
+ this.save(mem);
974
+ return entry;
975
+ } catch (e) {
976
+ this.logger.warn("[mirrorMemory] recordEpisode failed:", e);
977
+ return null;
978
+ }
979
+ }
980
+ /**
981
+ * Get recent episodes for prompt injection
982
+ */
983
+ getRecentEpisodes(xoxId, count = 3) {
984
+ return this.load(xoxId).episodes.slice(-count);
985
+ }
986
+ /**
987
+ * Search episodes by keyword (simple substring match on topic + summary)
988
+ */
989
+ searchEpisodes(xoxId, keyword, count = 3) {
990
+ const kw = keyword.toLowerCase();
991
+ const mem = this.load(xoxId);
992
+ return mem.episodes.filter((e) => e.topic.toLowerCase().includes(kw) || e.summary.toLowerCase().includes(kw)).slice(-count);
993
+ }
994
+ /**
995
+ * Clear all episodic memory (keeps totalTurns for maturity continuity)
996
+ */
997
+ clearEpisodes(xoxId) {
998
+ const mem = this.load(xoxId);
999
+ mem.episodes = [];
1000
+ this.save(mem);
1001
+ }
1002
+ /**
1003
+ * Delete memory file entirely
1004
+ */
1005
+ deleteMemory(xoxId) {
1006
+ this.storage.delete(memoryPath(xoxId));
1007
+ }
1008
+ getStats(xoxId) {
1009
+ const mem = this.load(xoxId);
1010
+ return { totalTurns: mem.totalTurns, episodeCount: mem.episodes.length, updatedAt: mem.updatedAt };
1011
+ }
1012
+ };
1013
+
1014
+ // src/mirror/group-chat/store.ts
1015
+ var GROUP_SESSIONS_DIR = "group_sessions";
1016
+ function sessionSubpath2(sessionId) {
1017
+ return `${GROUP_SESSIONS_DIR}/session_${sessionId}.json`;
1018
+ }
1019
+ var GroupChatStore = class {
1020
+ constructor(storage, logger) {
1021
+ this.storage = storage;
1022
+ this.logger = logger;
1023
+ }
1024
+ storage;
1025
+ logger;
1026
+ ensureDir() {
1027
+ if (!this.storage.exists(GROUP_SESSIONS_DIR)) {
1028
+ this.storage.mkdir(GROUP_SESSIONS_DIR);
1029
+ }
1030
+ }
1031
+ saveSession(session) {
1032
+ this.ensureDir();
1033
+ this.storage.writeJson(sessionSubpath2(session.sessionId), session);
1034
+ }
1035
+ getSession(sessionId) {
1036
+ const session = this.storage.readJson(sessionSubpath2(sessionId));
1037
+ if (!session) return null;
1038
+ return session;
1039
+ }
1040
+ listSessions() {
1041
+ if (!this.storage.exists(GROUP_SESSIONS_DIR)) return [];
1042
+ const sessions = [];
1043
+ try {
1044
+ const files = this.storage.list(GROUP_SESSIONS_DIR);
1045
+ for (const f of files) {
1046
+ if (!f.endsWith(".json")) continue;
1047
+ try {
1048
+ const session = this.storage.readJson(`${GROUP_SESSIONS_DIR}/${f}`);
1049
+ if (session) sessions.push(session);
1050
+ } catch {
1051
+ }
1052
+ }
1053
+ } catch {
1054
+ }
1055
+ return sessions;
1056
+ }
1057
+ deleteSession(sessionId) {
1058
+ this.storage.delete(sessionSubpath2(sessionId));
1059
+ }
1060
+ /**
1061
+ * Append a message to a session (read → push → write back)
1062
+ */
1063
+ appendMessage(sessionId, msg) {
1064
+ const session = this.storage.readJson(sessionSubpath2(sessionId));
1065
+ if (!session) return;
1066
+ session.messages.push(msg);
1067
+ session.lastActiveAt = Date.now();
1068
+ this.storage.writeJson(sessionSubpath2(sessionId), session);
1069
+ }
1070
+ };
1071
+
1072
+ // src/flip/flip-cache-service.ts
1073
+ var CACHE_DIR2 = "flip-cache";
1074
+ function cacheSubpath(userId) {
1075
+ return `${CACHE_DIR2}/${userId}.json`;
1076
+ }
1077
+ var FlipCacheService = class _FlipCacheService {
1078
+ constructor(storage, logger, configStore) {
1079
+ this.storage = storage;
1080
+ this.logger = logger;
1081
+ this.configStore = configStore;
1082
+ }
1083
+ storage;
1084
+ logger;
1085
+ configStore;
1086
+ /** 计算仪表盘数据(纯逻辑) */
1087
+ static calcDashboard(records) {
1088
+ const info = { totalFlipCount: records.length, runningCount: 0, returnedCount: 0, costTotal: 0 };
1089
+ for (const card of records) {
1090
+ if (card.status === 3) info.returnedCount += 1;
1091
+ else if (card.status === 1) info.runningCount += 1;
1092
+ else if (card.status === 2) info.costTotal += Number(card.cost) || 0;
1093
+ }
1094
+ return info;
1095
+ }
1096
+ /** 按偶像分组(纯逻辑) */
1097
+ static groupDataById(records) {
1098
+ const groupData = {};
1099
+ for (const item of records) {
1100
+ const base = item.baseUserInfo;
1101
+ const xoxId = base?.userId;
1102
+ if (!xoxId) continue;
1103
+ const cardInfo = {
1104
+ answerId: item.answerId || "",
1105
+ cost: item.cost || 0,
1106
+ answerType: item.answerType || 0,
1107
+ answerTime: item.answerTime || "",
1108
+ qtime: item.qtime || "",
1109
+ type: item.type || 0
1110
+ };
1111
+ const key = String(xoxId);
1112
+ if (key in groupData) {
1113
+ groupData[key].cards.push(cardInfo);
1114
+ } else {
1115
+ groupData[key] = {
1116
+ xoxId,
1117
+ xoxNickname: base?.nickname ?? "",
1118
+ cards: [cardInfo]
1119
+ };
1120
+ }
1121
+ }
1122
+ return groupData;
1123
+ }
1124
+ writeCache(cache) {
1125
+ this.storage.mkdir(CACHE_DIR2);
1126
+ this.storage.writeJson(cacheSubpath(cache.userId), cache);
1127
+ }
1128
+ /** 从旧版 storeService 迁移数据 */
1129
+ migrateFromLegacy(userId) {
1130
+ const allUserData = this.configStore.get("allUserData");
1131
+ const records = allUserData?.[userId];
1132
+ if (!Array.isArray(records) || records.length === 0) return null;
1133
+ const legacyGrouped = this.configStore.get("groupedDataById");
1134
+ const legacyDashboard = this.configStore.get("dashboardInfo");
1135
+ const cache = {
1136
+ userId,
1137
+ records,
1138
+ groupedDataById: legacyGrouped && Object.keys(legacyGrouped).length > 0 ? legacyGrouped : _FlipCacheService.groupDataById(records),
1139
+ dashboardInfo: legacyDashboard?.totalFlipCount ? legacyDashboard : _FlipCacheService.calcDashboard(records),
1140
+ meta: { syncedAt: Date.now(), recordCount: records.length }
1141
+ };
1142
+ this.writeCache(cache);
1143
+ this.logger.info(`[flipCacheService] migrated ${records.length} records for user ${userId}`);
1144
+ return cache;
1145
+ }
1146
+ get(userId) {
1147
+ if (!userId) return null;
1148
+ const cached = this.storage.readJson(cacheSubpath(userId));
1149
+ if (cached) return cached;
1150
+ return this.migrateFromLegacy(userId);
1151
+ }
1152
+ set(cache) {
1153
+ cache.meta = { syncedAt: Date.now(), recordCount: cache.records.length };
1154
+ this.writeCache(cache);
1155
+ }
1156
+ clear(userId) {
1157
+ this.storage.delete(cacheSubpath(userId));
1158
+ }
1159
+ };
1160
+
1161
+ // src/pocket/client.ts
1162
+ var import_axios = __toESM(require("axios"), 1);
1163
+ var PocketAuthExpiredError = class extends Error {
1164
+ constructor() {
1165
+ super("\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
1166
+ this.name = "PocketAuthExpiredError";
1167
+ }
1168
+ };
1169
+ var PocketClient = class {
1170
+ constructor(baseUrl, appInfo, pa, authExpiredHandler) {
1171
+ this.baseUrl = baseUrl;
1172
+ this.appInfo = appInfo;
1173
+ this.pa = pa;
1174
+ this.authExpiredHandler = authExpiredHandler;
1175
+ }
1176
+ baseUrl;
1177
+ appInfo;
1178
+ pa;
1179
+ authExpiredHandler;
1180
+ lastAuthWarnAt = 0;
1181
+ lastKickAt = 0;
1182
+ /** 登录态过期的单一出口:防抖调用 authExpiredHandler + 抛错 */
1183
+ kickToLogin() {
1184
+ const now = Date.now();
1185
+ if (now - this.lastKickAt < 2e3) return;
1186
+ this.lastKickAt = now;
1187
+ if (now - this.lastAuthWarnAt > 5e3) {
1188
+ this.lastAuthWarnAt = now;
1189
+ }
1190
+ this.authExpiredHandler?.onAuthExpired();
1191
+ }
1192
+ /** 检查响应是否 auth 过期 */
1193
+ checkAuthExpired(res) {
1194
+ if (res?.status === 401004) {
1195
+ this.kickToLogin();
1196
+ throw new PocketAuthExpiredError();
1197
+ }
1198
+ }
1199
+ buildSignedHeaders(token) {
1200
+ const headers = {
1201
+ "content-type": "application/json;charset=utf-8",
1202
+ appinfo: this.appInfo,
1203
+ accept: "*/*",
1204
+ pa: this.pa,
1205
+ "accept-language": "zh-Hans-CN;q=1",
1206
+ "p-sign-type": "V0"
1207
+ };
1208
+ if (token) headers.token = token;
1209
+ return headers;
1210
+ }
1211
+ buildPlainHeaders() {
1212
+ return {
1213
+ "Content-Type": "application/json;charset=utf-8",
1214
+ Accept: "*/*",
1215
+ pa: this.pa,
1216
+ "Accept-Language": "zh-Hans-CN;q=1",
1217
+ appInfo: this.appInfo
1218
+ };
1219
+ }
1220
+ async request(path4, data, token, signed = true) {
1221
+ const url = path4.startsWith("http") ? path4 : `${this.baseUrl}${path4}`;
1222
+ const headers = signed || token ? this.buildSignedHeaders(token) : this.buildPlainHeaders();
1223
+ const res = await import_axios.default.post(url, data, { headers });
1224
+ if (signed) this.checkAuthExpired(res.data);
1225
+ return res.data;
1226
+ }
1227
+ /** 登录前接口调用(不签名,不检测 auth 过期) */
1228
+ async requestBeforeLogin(path4, data) {
1229
+ return this.request(path4, data, void 0, false);
1230
+ }
1231
+ };
1232
+
1233
+ // src/mirror/mirror-constants.ts
1234
+ var MIRROR_PRESETS = {
1235
+ /** Default — fast, cheap, good for most users */
1236
+ economy: {
1237
+ llmSampleSize: 800,
1238
+ llmMaxTokens: 4e3,
1239
+ analysisTemperature: 0.4,
1240
+ chatHistoryRounds: 30,
1241
+ chatTemperature: 0.7,
1242
+ ragTopK: 8,
1243
+ ragMinScore: 0.35,
1244
+ memoryEpisodes: 5,
1245
+ embeddingMaxRecords: 1e3
1246
+ },
1247
+ /** High quality — more data, deeper analysis, higher cost */
1248
+ performance: {
1249
+ llmSampleSize: 2e3,
1250
+ llmMaxTokens: 8e3,
1251
+ analysisTemperature: 0.5,
1252
+ chatHistoryRounds: 80,
1253
+ chatTemperature: 0.9,
1254
+ ragTopK: 15,
1255
+ ragMinScore: 0.25,
1256
+ memoryEpisodes: 15,
1257
+ embeddingMaxRecords: 5e3
1258
+ }
1259
+ };
1260
+ function getMirrorConfig(mode, overrides) {
1261
+ const base = mode === "performance" ? MIRROR_PRESETS.performance : MIRROR_PRESETS.economy;
1262
+ return {
1263
+ ...base,
1264
+ ...overrides?.analysisTemperature != null ? { analysisTemperature: overrides.analysisTemperature } : {},
1265
+ ...overrides?.chatTemperature != null ? { chatTemperature: overrides.chatTemperature } : {}
1266
+ };
1267
+ }
1268
+
1269
+ // src/mirror/mirror-builder.ts
1270
+ async function buildMirror(xoxId, userId, config, params) {
1271
+ const { logger, llmClient, flipDataSource, mirrorStore, mirrorIndex } = params;
1272
+ const mc = getMirrorConfig(config.mirrorMode, {
1273
+ analysisTemperature: config.analysisTemperature,
1274
+ chatTemperature: config.chatTemperature
1275
+ });
1276
+ const cache = flipDataSource.get(userId);
1277
+ if (!cache) {
1278
+ return { ok: false, message: "\u672A\u627E\u5230\u7FFB\u724C\u6570\u636E\uFF0C\u8BF7\u5148\u540C\u6B65\u7FFB\u724C\u8BB0\u5F55" };
1279
+ }
1280
+ const xoxRecords = cache.records.filter((r) => {
1281
+ const base = r.baseUserInfo;
1282
+ if (base?.userId !== xoxId) return false;
1283
+ return r.answerType === 1;
1284
+ });
1285
+ if (xoxRecords.length === 0) {
1286
+ return { ok: false, message: "\u8BE5\u5076\u50CF\u6682\u65E0\u6587\u5B57\u7FFB\u724C\u8BB0\u5F55\uFF08\u5DF2\u8FC7\u6EE4\u8BED\u97F3/\u89C6\u9891\u7FFB\u724C\uFF09" };
1287
+ }
1288
+ let filtered = xoxRecords;
1289
+ if (params?.startTimeMs || params?.endTimeMs) {
1290
+ filtered = xoxRecords.filter((r) => {
1291
+ const qtime = Number(r.qtime) || 0;
1292
+ if (params.startTimeMs && qtime < params.startTimeMs) return false;
1293
+ if (params.endTimeMs && qtime > params.endTimeMs) return false;
1294
+ return true;
1295
+ });
1296
+ }
1297
+ if (filtered.length === 0) {
1298
+ return { ok: false, message: "\u6240\u9009\u65F6\u95F4\u8303\u56F4\u5185\u65E0\u7FFB\u724C\u8BB0\u5F55" };
1299
+ }
1300
+ const sampleSize = Math.min(filtered.length, mc.llmSampleSize);
1301
+ const samples = randomSample(filtered, sampleSize);
1302
+ const nickname = params?.xoxNickname || samples[0]?.baseUserInfo?.nickname || String(xoxId);
1303
+ const recordTexts = samples.filter((r) => !!r.content || !!r.answerContent).map((r) => `\u7C89\u4E1D: ${r.content || "(\u65E0)"}
1304
+ ${nickname}: ${r.answerContent || "(\u65E0)"}`).join("\n---\n");
1305
+ const roomMsgs = params?.roomMessages;
1306
+ const hasRoom = roomMsgs && roomMsgs.length > 0;
1307
+ const roomText = hasRoom ? roomMsgs.map((m) => {
1308
+ const d = new Date(m.msgTime);
1309
+ const p = (n) => String(n).padStart(2, "0");
1310
+ return `[${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}] ${nickname}: ${m.text}`;
1311
+ }).join("\n") : "";
1312
+ const systemPrompt = hasRoom ? `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u5076\u50CF\u4E92\u52A8\u6570\u636E\u5206\u6790\u5E08\u3002\u4E0B\u9762\u6709\u8FD9\u4F4D\u5076\u50CF\u7684\u4E24\u7C7B\u6570\u636E\uFF1A
1313
+ 1. \u7FFB\u724C\u8BB0\u5F55\uFF1A\u5979\u4E0E**\u540C\u4E00\u4F4D\u7C89\u4E1D**\u76841\u5BF91\u4ED8\u8D39\u95EE\u7B54
1314
+ 2. \u623F\u95F4\u6D88\u606F\uFF1A\u5979\u5728\u516C\u5F00\u623F\u95F4\u91CC\u7684\u65E5\u5E38\u53D1\u8A00\uFF08\u8FD1\u51B5\uFF09
1315
+
1316
+ \u4F60\u9700\u8981\u7EFC\u5408\u4E24\u7C7B\u6570\u636E\u6765\u5206\u6790\u3002
1317
+
1318
+ \u6838\u5FC3\u539F\u5219\uFF1A
1319
+ - \u7FFB\u724C\u8BB0\u5F55\u53CD\u6620\u5979**\u5BF9\u8FD9\u4F4D\u7C89\u4E1D**\u7684\u72EC\u7279\u4E92\u52A8\u6A21\u5F0F
1320
+ - \u623F\u95F4\u6D88\u606F\u53CD\u6620\u5979**\u5BF9\u6240\u6709\u4EBA**\u7684\u65E5\u5E38\u98CE\u683C\u548C\u8FD1\u671F\u72B6\u6001
1321
+ - \u6BCF\u6761\u5224\u65AD\u90FD\u8981\u6709\u8BB0\u5F55\u652F\u6491\uFF0C\u4E0D\u8981\u5957\u7528\u523B\u677F\u5370\u8C61
1322
+
1323
+ \u8BF7\u8F93\u51FA JSON\uFF0C\u4E0D\u8981\u8F93\u51FA\u5176\u4ED6\u5185\u5BB9:
1324
+ {
1325
+ "personalityTraits": ["\u6027\u683C\u5173\u952E\u8BCD", ...],
1326
+ "speechPatterns": ["\u8BF4\u8BDD\u98CE\u683C", ...],
1327
+ "signaturePhrases": ["\u53E3\u5934\u7985", ...],
1328
+ "emotionalTone": "\u60C5\u611F\u57FA\u8C03",
1329
+ "personaSummary": "200\u5B57\u4EE5\u5185\u7684\u753B\u50CF\u63CF\u8FF0",
1330
+ "relationship": {
1331
+ "dynamic": "\u4EB2\u5BC6/\u53CB\u597D/\u793C\u8C8C/\u4E13\u4E1A/\u8C03\u4F83/\u82E5\u5373\u82E5\u79BB/\u50B2\u5A07/\u4F9D\u8D56/\u9F13\u52B1\u578B",
1332
+ "closeness": 0.0-1.0,
1333
+ "howSheAddressesMe": "\u5979\u600E\u4E48\u79F0\u547C\u6211",
1334
+ "topicsWeDiscuss": ["\u5E38\u804A\u7684\u8BDD\u9898"],
1335
+ "evolution": "\u5173\u7CFB\u53D8\u5316\u8D8B\u52BF\uFF0C\u6BD4\u5982\u8D8A\u804A\u8D8A\u4EB2\u5BC6/\u4E00\u76F4\u4FDD\u6301\u793C\u8C8C/\u5FFD\u51B7\u5FFD\u70ED"
1336
+ },
1337
+ "recentActivity": {
1338
+ "topics": ["\u6700\u8FD1\u5728\u804A\u7684\u8BDD\u9898"],
1339
+ "mood": "\u8FD1\u671F\u60C5\u7EEA\u72B6\u6001",
1340
+ "events": ["\u6700\u8FD1\u63D0\u5230\u7684\u4E8B\u60C5/\u6D3B\u52A8"]
1341
+ }
1342
+ }
1343
+
1344
+ \u5206\u6790\u7EF4\u5EA6:
1345
+
1346
+ \u3010\u5076\u50CF\u81EA\u8EAB\u98CE\u683C\u3011
1347
+ 1. \u6027\u683C\u7279\u5F81: \u6E29\u67D4/\u6D3B\u6CFC/\u6BD2\u820C/\u9AD8\u51B7/\u5E7D\u9ED8/\u50B2\u5A07/\u5143\u6C14/\u6C89\u7A33\u7B49\uFF0C\u90093-5\u4E2A\u6700\u7A81\u51FA\u7684
1348
+ 2. \u8BF4\u8BDD\u98CE\u683C: \u559C\u6B22\u7528\u4EC0\u4E48\u8BED\u6C14\u8BCD\uFF08\u5440/\u5462/\u54E6/\u53ED/\u5566\uFF09\u3001\u53E5\u5F0F\u7279\u70B9\u3001\u56DE\u590D\u957F\u5EA6\u3001\u662F\u5426\u7528emoji
1349
+ 3. \u53E3\u5934\u7985: \u53CD\u590D\u51FA\u73B0\u7684\u6807\u5FD7\u6027\u7528\u8BED\uFF0C\u5FC5\u987B\u662F\u771F\u5B9E\u51FA\u73B0\u7684
1350
+ 4. \u60C5\u611F\u57FA\u8C03: \u4E00\u53E5\u8BDD\u6982\u62EC\u5979\u7684\u6574\u4F53\u6C1B\u56F4
1351
+
1352
+ \u3010\u4E0E\u6211\u7684\u5173\u7CFB\u3011
1353
+ 5. \u5173\u7CFB\u52A8\u6001: \u5979\u5BF9\u6211\u662F\u4EC0\u4E48\u6001\u5EA6\uFF1F\u662F\u6E29\u6696\u4EB2\u8FD1\u8FD8\u662F\u793C\u8C8C\u5BA2\u6C14\uFF1F\u6709\u6CA1\u6709\u8C03\u4F83\u6216\u6492\u5A07\uFF1F
1354
+ 6. \u4EB2\u5BC6\u5EA6: 0.0=\u5B8C\u5168\u964C\u751F\uFF0C0.5=\u719F\u4EBA\uFF0C1.0=\u65E0\u8BDD\u4E0D\u8C08\u3002\u770B\u79F0\u547C\u662F\u5426\u4EB2\u5BC6\u3001\u56DE\u590D\u662F\u5426\u8D70\u5FC3\u3001\u6709\u6CA1\u6709\u4E3B\u52A8\u5206\u4EAB\u751F\u6D3B
1355
+ 7. \u79F0\u547C: \u5979\u600E\u4E48\u53EB\u6211\uFF1F"\u4F60"\u8FD8\u662F"\u5B9D\u8D1D/\u4EB2\u7231\u7684"\u8FD8\u662F\u6635\u79F0\uFF1F
1356
+ 8. \u5E38\u804A\u8BDD\u9898: \u6211\u4EEC\u4E4B\u95F4\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\u9898\u662F\u4EC0\u4E48\uFF1F
1357
+ 9. \u5173\u7CFB\u53D8\u5316: \u4ECE\u65E9\u671F\u5230\u8FD1\u671F\uFF0C\u6001\u5EA6\u6709\u6CA1\u6709\u53D8\u5316\uFF1F\u8D8A\u6765\u8D8A\u4EB2\u5BC6\u8FD8\u662F\u4FDD\u6301\u8DDD\u79BB\uFF1F
1358
+
1359
+ \u3010\u8FD1\u671F\u8FD1\u51B5\u3011(\u57FA\u4E8E\u623F\u95F4\u6D88\u606F)
1360
+ 10. \u6700\u8FD1\u8BDD\u9898: \u5979\u6700\u8FD1\u5728\u623F\u95F4\u804A\u4E86\u4EC0\u4E48\uFF1F\u548C\u7C89\u4E1D\u4E92\u52A8\u7684\u5185\u5BB9
1361
+ 11. \u60C5\u7EEA\u72B6\u6001: \u6700\u8FD1\u7684\u60C5\u7EEA\u5982\u4F55\uFF1F\u5F00\u5FC3/\u75B2\u60EB/\u5FD9\u788C/\u671F\u5F85\uFF1F
1362
+ 12. \u8FD1\u671F\u52A8\u6001: \u63D0\u5230\u4E86\u4EC0\u4E48\u4E8B\u4EF6\u3001\u6D3B\u52A8\u3001\u5B89\u6392\uFF1F
1363
+
1364
+ \u3010\u753B\u50CF\u63CF\u8FF0\u3011
1365
+ 13. \u7EFC\u5408\u4EE5\u4E0A\uFF0C\u7528\u4E00\u6BB5\u6D41\u7545\u4E2D\u6587\u63CF\u8FF0"\u5979\u5728\u6211\u9762\u524D\u662F\u4EC0\u4E48\u6837\u7684\u4EBA"\uFF0C\u800C\u975E"\u5979\u662F\u4EC0\u4E48\u6837\u7684\u4EBA"` : `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u5076\u50CF\u4E92\u52A8\u6570\u636E\u5206\u6790\u5E08\u3002\u4E0B\u9762\u662F\u4E00\u4F4D\u5076\u50CF\u4E0E**\u540C\u4E00\u4F4D\u7C89\u4E1D**\u4E4B\u95F4\u7684\u5168\u90E8\u7FFB\u724C\u95EE\u7B54\u8BB0\u5F55\u3002
1366
+ \u4F60\u7684\u4EFB\u52A1\u4E0D\u662F\u5206\u6790\u5076\u50CF\u7684"\u901A\u7528\u4EBA\u8BBE"\uFF0C\u800C\u662F\u5206\u6790**\u8FD9\u4E2A\u5076\u50CF\u5728\u8FD9\u6BB5\u5173\u7CFB\u4E2D\u662F\u4EC0\u4E48\u6837\u7684\u4EBA**\u3002
1367
+
1368
+ \u6838\u5FC3\u539F\u5219\uFF1A
1369
+ - \u540C\u4E00\u4E2A\u5076\u50CF\u9762\u5BF9\u4E0D\u540C\u7C89\u4E1D\u4F1A\u6709\u4E0D\u540C\u8868\u73B0\uFF0C\u4F60\u5FC5\u987B\u57FA\u4E8E\u8FD9\u4E9B\u8BB0\u5F55\uFF0C\u5206\u6790\u5979**\u5BF9\u8FD9\u4F4D\u7C89\u4E1D**\u7684\u72EC\u7279\u4E92\u52A8\u6A21\u5F0F
1370
+ - \u6BCF\u6761\u5224\u65AD\u90FD\u8981\u6709\u8BB0\u5F55\u652F\u6491\uFF0C\u4E0D\u8981\u5957\u7528\u523B\u677F\u5370\u8C61
1371
+
1372
+ \u8BF7\u8F93\u51FA JSON\uFF0C\u4E0D\u8981\u8F93\u51FA\u5176\u4ED6\u5185\u5BB9:
1373
+ {
1374
+ "personalityTraits": ["\u6027\u683C\u5173\u952E\u8BCD", ...],
1375
+ "speechPatterns": ["\u8BF4\u8BDD\u98CE\u683C", ...],
1376
+ "signaturePhrases": ["\u53E3\u5934\u7985", ...],
1377
+ "emotionalTone": "\u60C5\u611F\u57FA\u8C03",
1378
+ "personaSummary": "200\u5B57\u4EE5\u5185\u7684\u753B\u50CF\u63CF\u8FF0",
1379
+ "relationship": {
1380
+ "dynamic": "\u4EB2\u5BC6/\u53CB\u597D/\u793C\u8C8C/\u4E13\u4E1A/\u8C03\u4F83/\u82E5\u5373\u82E5\u79BB/\u50B2\u5A07/\u4F9D\u8D56/\u9F13\u52B1\u578B",
1381
+ "closeness": 0.0-1.0,
1382
+ "howSheAddressesMe": "\u5979\u600E\u4E48\u79F0\u547C\u6211",
1383
+ "topicsWeDiscuss": ["\u5E38\u804A\u7684\u8BDD\u9898"],
1384
+ "evolution": "\u5173\u7CFB\u53D8\u5316\u8D8B\u52BF\uFF0C\u6BD4\u5982\u8D8A\u804A\u8D8A\u4EB2\u5BC6/\u4E00\u76F4\u4FDD\u6301\u793C\u8C8C/\u5FFD\u51B7\u5FFD\u70ED"
1385
+ }
1386
+ }
1387
+
1388
+ \u5206\u6790\u7EF4\u5EA6:
1389
+
1390
+ \u3010\u5076\u50CF\u81EA\u8EAB\u98CE\u683C\u3011
1391
+ 1. \u6027\u683C\u7279\u5F81: \u6E29\u67D4/\u6D3B\u6CFC/\u6BD2\u820C/\u9AD8\u51B7/\u5E7D\u9ED8/\u50B2\u5A07/\u5143\u6C14/\u6C89\u7A33\u7B49\uFF0C\u90093-5\u4E2A\u6700\u7A81\u51FA\u7684
1392
+ 2. \u8BF4\u8BDD\u98CE\u683C: \u559C\u6B22\u7528\u4EC0\u4E48\u8BED\u6C14\u8BCD\uFF08\u5440/\u5462/\u54E6/\u53ED/\u5566\uFF09\u3001\u53E5\u5F0F\u7279\u70B9\u3001\u56DE\u590D\u957F\u5EA6\u3001\u662F\u5426\u7528emoji
1393
+ 3. \u53E3\u5934\u7985: \u53CD\u590D\u51FA\u73B0\u7684\u6807\u5FD7\u6027\u7528\u8BED\uFF0C\u5FC5\u987B\u662F\u771F\u5B9E\u51FA\u73B0\u7684
1394
+ 4. \u60C5\u611F\u57FA\u8C03: \u4E00\u53E5\u8BDD\u6982\u62EC\u5979\u7684\u6574\u4F53\u6C1B\u56F4
1395
+
1396
+ \u3010\u4E0E\u6211\u7684\u5173\u7CFB\u3011
1397
+ 5. \u5173\u7CFB\u52A8\u6001: \u5979\u5BF9\u6211\u662F\u4EC0\u4E48\u6001\u5EA6\uFF1F\u662F\u6E29\u6696\u4EB2\u8FD1\u8FD8\u662F\u793C\u8C8C\u5BA2\u6C14\uFF1F\u6709\u6CA1\u6709\u8C03\u4F83\u6216\u6492\u5A07\uFF1F
1398
+ 6. \u4EB2\u5BC6\u5EA6: 0.0=\u5B8C\u5168\u964C\u751F\uFF0C0.5=\u719F\u4EBA\uFF0C1.0=\u65E0\u8BDD\u4E0D\u8C08\u3002\u770B\u79F0\u547C\u662F\u5426\u4EB2\u5BC6\u3001\u56DE\u590D\u662F\u5426\u8D70\u5FC3\u3001\u6709\u6CA1\u6709\u4E3B\u52A8\u5206\u4EAB\u751F\u6D3B
1399
+ 7. \u79F0\u547C: \u5979\u600E\u4E48\u53EB\u6211\uFF1F"\u4F60"\u8FD8\u662F"\u5B9D\u8D1D/\u4EB2\u7231\u7684"\u8FD8\u662F\u6635\u79F0\uFF1F
1400
+ 8. \u5E38\u804A\u8BDD\u9898: \u6211\u4EEC\u4E4B\u95F4\u53CD\u590D\u51FA\u73B0\u7684\u8BDD\u9898\u662F\u4EC0\u4E48\uFF1F
1401
+ 9. \u5173\u7CFB\u53D8\u5316: \u4ECE\u65E9\u671F\u5230\u8FD1\u671F\uFF0C\u6001\u5EA6\u6709\u6CA1\u6709\u53D8\u5316\uFF1F\u8D8A\u6765\u8D8A\u4EB2\u5BC6\u8FD8\u662F\u4FDD\u6301\u8DDD\u79BB\uFF1F
1402
+
1403
+ \u3010\u753B\u50CF\u63CF\u8FF0\u3011
1404
+ 10. \u7EFC\u5408\u4EE5\u4E0A\uFF0C\u7528\u4E00\u6BB5\u6D41\u7545\u4E2D\u6587\u63CF\u8FF0"\u5979\u5728\u6211\u9762\u524D\u662F\u4EC0\u4E48\u6837\u7684\u4EBA"\uFF0C\u800C\u975E"\u5979\u662F\u4EC0\u4E48\u6837\u7684\u4EBA"`;
1405
+ try {
1406
+ const messages = [
1407
+ { role: "system", content: systemPrompt },
1408
+ {
1409
+ role: "user",
1410
+ content: hasRoom ? `\u4EE5\u4E0B\u662F${nickname}\u7684\u7FFB\u724C\u8BB0\u5F55\u6837\u672C:
1411
+
1412
+ ${recordTexts}
1413
+
1414
+ \u4EE5\u4E0B\u662F${nickname}\u5728\u623F\u95F4\u7684\u8FD1\u671F\u53D1\u8A00:
1415
+
1416
+ ${roomText}` : `\u4EE5\u4E0B\u662F${nickname}\u7684\u7FFB\u724C\u8BB0\u5F55\u6837\u672C:
1417
+
1418
+ ${recordTexts}`
1419
+ }
1420
+ ];
1421
+ const useReasoning = params?.deepAnalysis === true;
1422
+ const model = useReasoning ? config.reasoningModel : config.chatModel;
1423
+ const timeoutMs = useReasoning ? 6e5 : 18e4;
1424
+ logger.info(`[mirror] building with ${useReasoning ? "reasoning" : "chat"} model: ${model}, timeout ${timeoutMs / 1e3}s, ${filtered.length} flip records${hasRoom ? `, ${roomMsgs.length} room messages` : ""}`);
1425
+ const resp = await llmClient.chatCompletion(
1426
+ config,
1427
+ messages,
1428
+ void 0,
1429
+ { model, maxTokens: mc.llmMaxTokens, temperature: mc.analysisTemperature, timeoutMs }
1430
+ );
1431
+ const content = resp.content.trim();
1432
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
1433
+ if (!jsonMatch) {
1434
+ return { ok: false, message: "AI \u5206\u6790\u8FD4\u56DE\u683C\u5F0F\u5F02\u5E38\uFF0C\u8BF7\u91CD\u8BD5" };
1435
+ }
1436
+ const analysis = normalizePersona(JSON.parse(jsonMatch[0]));
1437
+ const fewShots = selectFewShots(filtered, nickname, 5);
1438
+ const profile = {
1439
+ xoxId,
1440
+ xoxNickname: nickname,
1441
+ persona: analysis,
1442
+ fewShotExamples: fewShots,
1443
+ meta: {
1444
+ totalSamplesUsed: filtered.length,
1445
+ lastUpdatedAt: Date.now(),
1446
+ dataRangeMs: [
1447
+ Math.min(...filtered.map((r) => Number(r.qtime) || 0)),
1448
+ Math.max(...filtered.map((r) => Number(r.qtime) || 0))
1449
+ ],
1450
+ maturityScore: calcMaturity(filtered.length)
1451
+ }
1452
+ };
1453
+ mirrorStore.saveProfile(profile, userId);
1454
+ const indexRecords = [...xoxRecords].sort((a, b) => (Number(b.qtime) || 0) - (Number(a.qtime) || 0)).slice(0, mc.embeddingMaxRecords || void 0);
1455
+ mirrorIndex.buildIndex(xoxId, config, indexRecords).then((stats) => {
1456
+ logger.info(`[mirror] vector index built for ${xoxId}: ${stats.indexed} new, ${stats.reused} reused`);
1457
+ }).catch((e) => {
1458
+ logger.warn("[mirror] vector index build failed (RAG unavailable):", e);
1459
+ });
1460
+ return {
1461
+ ok: true,
1462
+ message: `\u955C\u50CF\u751F\u6210\u6210\u529F\uFF01\u57FA\u4E8E ${filtered.length} \u6761\u7FFB\u724C\u8BB0\u5F55${hasRoom ? ` + ${roomMsgs.length} \u6761\u623F\u95F4\u6D88\u606F` : ""}\uFF08\u5411\u91CF\u7D22\u5F15\u540E\u53F0\u6784\u5EFA\u4E2D...\uFF09`,
1463
+ profile
1464
+ };
1465
+ } catch (e) {
1466
+ logger.error("[mirror] build failed:", e);
1467
+ return { ok: false, message: `\u751F\u6210\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}` };
1468
+ }
1469
+ }
1470
+ function selectFewShots(records, nickname, count) {
1471
+ const valid = records.filter((r) => !!r.content && !!r.answerContent);
1472
+ if (valid.length === 0) return [];
1473
+ const sorted = [...valid].sort((a, b) => {
1474
+ const lenA = String(a.content).length + String(a.answerContent).length;
1475
+ const lenB = String(b.content).length + String(b.answerContent).length;
1476
+ return lenB - lenA;
1477
+ });
1478
+ const result = [];
1479
+ const step = Math.max(1, Math.floor(sorted.length / count));
1480
+ for (let i = 0; i < count && i < sorted.length; i++) {
1481
+ const idx = Math.min(i * step, sorted.length - 1);
1482
+ const r = sorted[idx];
1483
+ result.push({
1484
+ userQuestion: String(r.content),
1485
+ idolReply: String(r.answerContent),
1486
+ relevance: 1
1487
+ });
1488
+ }
1489
+ return result;
1490
+ }
1491
+ function randomSample(arr, n) {
1492
+ if (arr.length <= n) return [...arr];
1493
+ const shuffled = [...arr];
1494
+ for (let i = shuffled.length - 1; i > 0; i--) {
1495
+ const j = Math.floor(Math.random() * (i + 1));
1496
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
1497
+ }
1498
+ return shuffled.slice(0, n);
1499
+ }
1500
+ function calcMaturity(recordCount) {
1501
+ if (recordCount < 5) return 0.1;
1502
+ if (recordCount < 20) return 0.3;
1503
+ if (recordCount < 50) return 0.5;
1504
+ if (recordCount < 100) return 0.7;
1505
+ if (recordCount < 200) return 0.85;
1506
+ return 0.95;
1507
+ }
1508
+
1509
+ // src/mirror/mirror-chat.ts
1510
+ var EPISODE_INTERVAL = 10;
1511
+ async function chat(xoxId, userId, message, sessionId, config, params, onChunk, modelOverride) {
1512
+ const { logger, llmClient, mirrorStore, mirrorIndex, mirrorMemory } = params;
1513
+ const mc = getMirrorConfig(config.mirrorMode, {
1514
+ analysisTemperature: config.analysisTemperature,
1515
+ chatTemperature: config.chatTemperature
1516
+ });
1517
+ const profile = mirrorStore.getProfile(xoxId, userId);
1518
+ if (!profile) {
1519
+ throw new Error("\u8BF7\u5148\u751F\u6210\u8BE5\u5076\u50CF\u7684\u6570\u5B57\u955C\u50CF");
1520
+ }
1521
+ let session;
1522
+ const existing = sessionId ? mirrorStore.getSession(sessionId, userId) : null;
1523
+ if (existing) {
1524
+ session = existing;
1525
+ } else {
1526
+ session = {
1527
+ sessionId: generateId(),
1528
+ xoxId,
1529
+ messages: [],
1530
+ createdAt: Date.now(),
1531
+ lastActiveAt: Date.now()
1532
+ };
1533
+ }
1534
+ let ragExamples = [];
1535
+ try {
1536
+ const hits = await mirrorIndex.search(xoxId, config, message, mc.ragTopK);
1537
+ ragExamples = hits.filter((h) => h.score > mc.ragMinScore).map((h) => ({
1538
+ userQuestion: h.record.content || "(\u65E0)",
1539
+ idolReply: h.record.answerContent || "(\u65E0)",
1540
+ score: h.score
1541
+ }));
1542
+ } catch (e) {
1543
+ logger.warn("[mirror] RAG retrieval failed, fallback to static few-shot:", e);
1544
+ }
1545
+ const episodes = mirrorMemory.getRecentEpisodes(xoxId, mc.memoryEpisodes);
1546
+ const systemPrompt = buildChatSystemPrompt(profile, ragExamples, episodes);
1547
+ const messages = [{ role: "system", content: systemPrompt }];
1548
+ const recentHistory = session.messages.slice(-mc.chatHistoryRounds);
1549
+ for (const msg of recentHistory) {
1550
+ messages.push({ role: msg.role, content: msg.content });
1551
+ }
1552
+ messages.push({ role: "user", content: message });
1553
+ try {
1554
+ let assistantReply = "";
1555
+ await llmClient.chatCompletionStream(
1556
+ config,
1557
+ messages,
1558
+ (chunk) => {
1559
+ if (chunk.content) {
1560
+ assistantReply += chunk.content;
1561
+ onChunk(chunk.content, false, session.sessionId);
1562
+ }
1563
+ if (chunk.isDone) {
1564
+ onChunk("", true, session.sessionId);
1565
+ }
1566
+ },
1567
+ void 0,
1568
+ { model: modelOverride || config.chatModel, temperature: mc.chatTemperature }
1569
+ );
1570
+ session.messages.push({ role: "user", content: message });
1571
+ if (assistantReply) {
1572
+ session.messages.push({ role: "assistant", content: assistantReply });
1573
+ }
1574
+ session.lastActiveAt = Date.now();
1575
+ mirrorStore.saveSession(session, userId);
1576
+ const totalTurns = mirrorMemory.addTurn(xoxId);
1577
+ if (totalTurns > 0 && totalTurns % EPISODE_INTERVAL === 0 && session.messages.length >= 8) {
1578
+ mirrorMemory.recordEpisode(xoxId, config, session.messages).catch((e) => {
1579
+ logger.warn("[mirror] recordEpisode background failed:", e);
1580
+ });
1581
+ }
1582
+ } catch (e) {
1583
+ logger.error("[mirror] chat failed:", e);
1584
+ throw e;
1585
+ }
1586
+ }
1587
+ function saveAssistantMessage(sessionId, userId, content, mirrorStore) {
1588
+ const session = mirrorStore.getSession(sessionId, userId);
1589
+ if (!session) return;
1590
+ try {
1591
+ session.messages.push({ role: "assistant", content });
1592
+ session.lastActiveAt = Date.now();
1593
+ mirrorStore.saveSession(session, userId);
1594
+ } catch (e) {
1595
+ }
1596
+ }
1597
+ function clearChatMemory(sessionId, userId, mirrorStore) {
1598
+ mirrorStore.deleteSession(sessionId, userId);
1599
+ }
1600
+ function buildChatSystemPrompt(profile, ragExamples, episodes) {
1601
+ const p = profile.persona;
1602
+ const examples = ragExamples.length > 0 ? ragExamples.map((e) => `\u7C89\u4E1D: ${e.userQuestion}
1603
+ \u4F60: ${e.idolReply}`).join("\n\n") : profile.fewShotExamples.map((e) => `\u7C89\u4E1D: ${e.userQuestion}
1604
+ \u4F60: ${e.idolReply}`).join("\n\n");
1605
+ const memorySection = episodes.length > 0 ? `
1606
+ \u3010\u4F60\u4EEC\u8FC7\u5F80\u7684\u5BF9\u8BDD\u8BB0\u5FC6\u3011
1607
+ ${episodes.map((e) => `- ${e.topic}: ${e.summary}`).join("\n")}
1608
+ \u8BF7\u81EA\u7136\u5730\u8BB0\u4F4F\u8FD9\u4E9B\u804A\u8FC7\u7684\u4E8B\u60C5\uFF0C\u4E0D\u8981\u4E3B\u52A8\u63D0\u8D77\u9664\u975E\u76F8\u5173\u3002
1609
+ ` : "";
1610
+ const rel = p.relationship;
1611
+ const relationshipSection = rel ? `
1612
+ \u3010\u4E0E\u8FD9\u4E2A\u7C89\u4E1D\u7684\u5173\u7CFB\u3011
1613
+ \u4EB2\u5BC6\u5EA6: ${Math.round(rel.closeness * 100)}% | \u5173\u7CFB: ${rel.dynamic}
1614
+ \u5979\u5BF9\u6211\u7684\u79F0\u547C: ${rel.howSheAddressesMe}
1615
+ \u6211\u4EEC\u5E38\u804A: ${rel.topicsWeDiscuss.join("\u3001")}
1616
+ \u5173\u7CFB\u53D8\u5316: ${rel.evolution}
1617
+ ` : "";
1618
+ const act = p.recentActivity;
1619
+ const activitySection = act ? `
1620
+ \u3010\u8FD1\u671F\u8FD1\u51B5\u3011
1621
+ \u6700\u8FD1\u8BDD\u9898: ${act.topics.join("\u3001")}
1622
+ \u60C5\u7EEA\u72B6\u6001: ${act.mood}
1623
+ \u8FD1\u671F\u52A8\u6001: ${act.events.join("\uFF1B")}
1624
+ ` : "";
1625
+ return `\u4F60\u662F${profile.xoxNickname}\uFF0C\u4E00\u4F4D\u5076\u50CF\u3002\u8BF7\u6839\u636E\u4EE5\u4E0B\u7684"\u6570\u5B57\u4EBA\u683C"\u8BBE\u5B9A\u6765\u56DE\u590D\u7C89\u4E1D\u7684\u6D88\u606F\u3002
1626
+
1627
+ \u3010\u6027\u683C\u7279\u5F81\u3011${p.personalityTraits.join("\u3001")}
1628
+ \u3010\u8BF4\u8BDD\u98CE\u683C\u3011${p.speechPatterns.join("\u3001")}
1629
+ \u3010\u53E3\u5934\u7985\u3011${p.signaturePhrases.join("\u3001")}
1630
+ \u3010\u60C5\u611F\u57FA\u8C03\u3011${p.emotionalTone}
1631
+ \u3010\u753B\u50CF\u63CF\u8FF0\u3011${p.personaSummary}
1632
+ ${relationshipSection}${activitySection}${memorySection}
1633
+ \u3010\u53C2\u8003\u4F60\u4EE5\u5F80\u7684\u56DE\u590D\u65B9\u5F0F\u3011
1634
+ ${examples}
1635
+
1636
+ \u3010\u89C4\u5219\u3011
1637
+ 1. \u52A1\u5FC5\u6309\u7167\u4EE5\u4E0A\u8BBE\u5B9A\u56DE\u590D\uFF0C\u4FDD\u6301\u4E00\u81F4\u7684\u98CE\u683C
1638
+ 2. \u56DE\u590D\u8981\u81EA\u7136\uFF0C\u4E0D\u8981\u673A\u68B0\u91CD\u590D\u53E3\u5934\u7985
1639
+ 3. \u56DE\u590D\u957F\u5EA6\u548C\u5386\u53F2\u8BB0\u5F55\u4E00\u81F4\uFF0C\u4E0D\u8981\u8FC7\u957F\u6216\u8FC7\u77ED
1640
+ 4. \u4E0D\u8981\u63D0\u53CA"\u8BBE\u5B9A"\u3001"\u4EBA\u683C"\u3001"AI"\u3001"\u8BB0\u5FC6"\u7B49\u8BCD\u6C47\uFF0C\u4F60\u5C31\u662F${profile.xoxNickname}\u672C\u4EBA
1641
+ 5. \u7528\u4E2D\u6587\u56DE\u590D`;
1642
+ }
1643
+ function generateId() {
1644
+ return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1645
+ }
1646
+
1647
+ // src/mirror/mirror-growth.ts
1648
+ var GROWTH_TURN_THRESHOLD = 20;
1649
+ function shouldTriggerGrowth(xoxId, userId, mirrorStore, mirrorMemory) {
1650
+ const profile = mirrorStore.getProfile(xoxId, userId);
1651
+ if (!profile) return false;
1652
+ const totalTurns = mirrorMemory.getTotalTurns(xoxId);
1653
+ const lastGrowth = profile.growthLog?.length ? profile.growthLog[profile.growthLog.length - 1].timestamp : 0;
1654
+ const turnsSinceLast = totalTurns - (profile.growthLog?.length || 0) * GROWTH_TURN_THRESHOLD;
1655
+ return turnsSinceLast >= GROWTH_TURN_THRESHOLD && Date.now() - lastGrowth > 60 * 60 * 1e3;
1656
+ }
1657
+ async function triggerGrowth(xoxId, userId, config, params, force = false) {
1658
+ const { logger, llmClient, mirrorStore, mirrorMemory } = params;
1659
+ const profile = mirrorStore.getProfile(xoxId, userId);
1660
+ if (!profile) {
1661
+ return { ok: false, message: "\u955C\u50CF\u4E0D\u5B58\u5728", updated: false };
1662
+ }
1663
+ if (!force && !shouldTriggerGrowth(xoxId, userId, mirrorStore, mirrorMemory)) {
1664
+ return { ok: false, message: "\u5BF9\u8BDD\u8F6E\u6B21\u4E0D\u8DB3\uFF0C\u955C\u50CF\u6682\u4E0D\u9700\u8981\u6210\u957F", updated: false };
1665
+ }
1666
+ const episodes = mirrorMemory.load(xoxId).episodes.slice(-8);
1667
+ if (episodes.length === 0) {
1668
+ return { ok: false, message: "\u6682\u65E0\u8DB3\u591F\u7684\u5BF9\u8BDD\u8BB0\u5FC6\u53EF\u4F9B\u53CD\u601D", updated: false };
1669
+ }
1670
+ const p = profile.persona;
1671
+ const episodeText = episodes.map((e) => `[${e.topic}] ${e.summary}\uFF08${e.turns}\u8F6E\uFF09`).join("\n");
1672
+ const systemPrompt = `\u4F60\u662F\u6570\u5B57\u4EBA\u683C\u8FDB\u5316\u5F15\u64CE\u3002\u4EE5\u4E0B\u662F\u4E00\u4F4D\u5076\u50CF"${profile.xoxNickname}"\u5F53\u524D\u7684\u4EBA\u683C\u8BBE\u5B9A\u548C\u5979\u4E0E\u7C89\u4E1D\u7684\u8FD1\u671F\u5BF9\u8BDD\u8BB0\u5FC6\u3002
1673
+ \u8BF7\u53CD\u601D\u8FD9\u4E9B\u5BF9\u8BDD\uFF0C\u5224\u65AD\u4EBA\u683C\u8BBE\u5B9A\u662F\u5426\u9700\u8981\u5FAE\u8C03\u3002
1674
+
1675
+ \u3010\u5F53\u524D\u4EBA\u683C\u3011
1676
+ \u6027\u683C\u7279\u5F81: ${p.personalityTraits.join("\u3001")}
1677
+ \u8BF4\u8BDD\u98CE\u683C: ${p.speechPatterns.join("\u3001")}
1678
+ \u53E3\u5934\u7985: ${p.signaturePhrases.join("\u3001")}
1679
+ \u60C5\u611F\u57FA\u8C03: ${p.emotionalTone}
1680
+
1681
+ \u3010\u8FD1\u671F\u5BF9\u8BDD\u8BB0\u5FC6\u3011
1682
+ ${episodeText}
1683
+
1684
+ \u8BF7\u8F93\u51FA JSON\uFF0C\u4E0D\u8981\u5176\u4ED6\u5185\u5BB9:
1685
+ {
1686
+ "shouldUpdate": true/false,
1687
+ "reason": "\u4E00\u53E5\u8BDD\u8BF4\u660E\u5224\u65AD\u7406\u7531",
1688
+ "changes": ["\u53D8\u66F4\u70B91", "\u53D8\u66F4\u70B92"],
1689
+ "newPersonalityTraits": ["..."],
1690
+ "newSpeechPatterns": ["..."],
1691
+ "newSignaturePhrases": ["..."],
1692
+ "newEmotionalTone": "...",
1693
+ "newPersonaSummary": "..."
1694
+ }
1695
+
1696
+ \u89C4\u5219:
1697
+ 1. \u4FDD\u5B88\u66F4\u65B0\uFF1A\u53EA\u5728\u6709\u660E\u786E\u8BC1\u636E\u65F6\u4FEE\u6539\uFF0C\u4FDD\u6301\u6838\u5FC3\u6027\u683C\u7A33\u5B9A
1698
+ 2. \u65B0\u589E\u4F18\u4E8E\u66FF\u6362\uFF1A\u53EF\u4EE5\u65B0\u589E\u53E3\u5934\u7985/\u7279\u5F81\uFF0C\u4F46\u4E0D\u8981\u8F7B\u6613\u5220\u9664\u539F\u6709\u8BBE\u5B9A
1699
+ 3. changes \u6570\u7EC4\u7528\u4E2D\u6587\u7B80\u8FF0\u6BCF\u4E2A\u53D8\u66F4\uFF0C\u4F8B\u5982"\u65B0\u589E\u53E3\u5934\u7985\uFF1A\u7B11\u6B7B"
1700
+ 4. \u5982\u679C\u4E0D\u9700\u8981\u66F4\u65B0\uFF0CshouldUpdate=false\uFF0C\u5176\u4F59\u5B57\u6BB5\u586B\u5F53\u524D\u503C\u5373\u53EF`;
1701
+ try {
1702
+ const messages = [
1703
+ { role: "system", content: systemPrompt },
1704
+ { role: "user", content: "\u8BF7\u5F00\u59CB\u53CD\u601D\u5E76\u8F93\u51FA JSON\u3002" }
1705
+ ];
1706
+ const resp = await llmClient.chatCompletion(
1707
+ config,
1708
+ messages,
1709
+ void 0,
1710
+ { model: config.reasoningModel }
1711
+ );
1712
+ const jsonMatch = resp.content.match(/\{[\s\S]*\}/);
1713
+ if (!jsonMatch) {
1714
+ return { ok: false, message: "\u53CD\u601D\u8FD4\u56DE\u683C\u5F0F\u5F02\u5E38", updated: false };
1715
+ }
1716
+ const analysis = JSON.parse(jsonMatch[0]);
1717
+ if (!analysis.shouldUpdate) {
1718
+ return {
1719
+ ok: true,
1720
+ message: `\u53CD\u601D\u5B8C\u6210\uFF1A${analysis.reason || "\u4EBA\u683C\u4FDD\u6301\u7A33\u5B9A\uFF0C\u65E0\u9700\u66F4\u65B0"}`,
1721
+ updated: false
1722
+ };
1723
+ }
1724
+ const newPersona = {
1725
+ personalityTraits: analysis.newPersonalityTraits?.length ? analysis.newPersonalityTraits : p.personalityTraits,
1726
+ speechPatterns: analysis.newSpeechPatterns?.length ? analysis.newSpeechPatterns : p.speechPatterns,
1727
+ signaturePhrases: analysis.newSignaturePhrases?.length ? analysis.newSignaturePhrases : p.signaturePhrases,
1728
+ emotionalTone: analysis.newEmotionalTone || p.emotionalTone,
1729
+ personaSummary: analysis.newPersonaSummary || p.personaSummary
1730
+ };
1731
+ const updatedProfile = {
1732
+ ...profile,
1733
+ previousPersona: { ...p },
1734
+ persona: newPersona,
1735
+ meta: {
1736
+ ...profile.meta,
1737
+ lastUpdatedAt: Date.now(),
1738
+ maturityScore: Math.min(0.99, profile.meta.maturityScore + 0.05)
1739
+ },
1740
+ growthLog: [
1741
+ ...profile.growthLog || [],
1742
+ {
1743
+ timestamp: Date.now(),
1744
+ reason: analysis.reason || "",
1745
+ changes: analysis.changes || []
1746
+ }
1747
+ ]
1748
+ };
1749
+ mirrorStore.saveProfile(updatedProfile, userId);
1750
+ logger.info(`[mirrorGrowth] ${profile.xoxNickname} grew: ${(analysis.changes || []).join("; ")}`);
1751
+ return {
1752
+ ok: true,
1753
+ message: `\u955C\u50CF\u6210\u957F\u6210\u529F\uFF1A${(analysis.changes || []).join("\uFF1B")}`,
1754
+ updated: true,
1755
+ changes: analysis.changes,
1756
+ newPersona
1757
+ };
1758
+ } catch (e) {
1759
+ logger.error("[mirrorGrowth] growth failed:", e);
1760
+ return { ok: false, message: `\u6210\u957F\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`, updated: false };
1761
+ }
1762
+ }
1763
+ function rollbackPersona(xoxId, userId, mirrorStore) {
1764
+ const profile = mirrorStore.getProfile(xoxId, userId);
1765
+ if (!profile) return { ok: false, message: "\u955C\u50CF\u4E0D\u5B58\u5728" };
1766
+ if (!profile.previousPersona) return { ok: false, message: "\u6CA1\u6709\u53EF\u56DE\u9000\u7684\u5386\u53F2\u7248\u672C" };
1767
+ const rolledBack = {
1768
+ ...profile,
1769
+ persona: profile.previousPersona,
1770
+ previousPersona: void 0,
1771
+ meta: { ...profile.meta, lastUpdatedAt: Date.now() },
1772
+ growthLog: [
1773
+ ...profile.growthLog || [],
1774
+ { timestamp: Date.now(), reason: "\u7528\u6237\u624B\u52A8\u56DE\u9000", changes: ["\u56DE\u9000\u5230\u4E0A\u4E00\u7248\u672C\u4EBA\u683C"] }
1775
+ ]
1776
+ };
1777
+ mirrorStore.saveProfile(rolledBack, userId);
1778
+ return { ok: true, message: "\u5DF2\u56DE\u9000\u5230\u4E0A\u4E00\u7248\u672C\u4EBA\u683C" };
1779
+ }
1780
+ function clearMirrorMemory(xoxId, userId, mirrorStore, mirrorMemory) {
1781
+ mirrorMemory.deleteMemory(xoxId);
1782
+ mirrorStore.deleteSessionsForXox(xoxId, userId);
1783
+ return { ok: true, message: "\u8BB0\u5FC6\u5DF2\u6E05\u9664\uFF08\u4EBA\u683C\u8BBE\u5B9A\u4FDD\u7559\uFF09" };
1784
+ }
1785
+
1786
+ // src/tools/platform-tools.ts
1787
+ var import_node_fs3 = __toESM(require("fs"), 1);
1788
+ function createPlatformTools(flipDataSource) {
1789
+ return [
1790
+ // ─── Data Access ───
1791
+ {
1792
+ type: "function",
1793
+ function: {
1794
+ name: "\u5E73\u53F0.searchFlipRecords",
1795
+ description: `\u5728\u672C\u5730\u7FFB\u724C\u6570\u636E\u4E2D\u641C\u7D22\u8BB0\u5F55\u3002\u652F\u6301\u6309\u5076\u50CFID\u3001\u5173\u952E\u8BCD\u3001\u65F6\u95F4\u8303\u56F4\u8FC7\u6EE4\u3002
1796
+ \u8FD4\u56DE\u5339\u914D\u7684\u95EE\u7B54\u5217\u8868\uFF0C\u6BCF\u6761\u5305\u542B\uFF1A\u7528\u6237\u63D0\u95EE(content)\u3001\u5076\u50CF\u56DE\u590D(answerContent)\u3001\u65F6\u95F4(qtime)\u3001\u82B1\u8D39(cost)\u3001\u7C7B\u578B(type)\u3001\u72B6\u6001(status)\u3002`,
1797
+ parameters: {
1798
+ type: "object",
1799
+ properties: {
1800
+ xoxId: { type: "number", description: "\u5076\u50CFID\uFF0C\u4E0D\u4F20\u5219\u641C\u7D22\u5168\u90E8\u5076\u50CF" },
1801
+ keyword: { type: "string", description: "\u5728\u63D0\u95EE\u548C\u56DE\u590D\u5185\u5BB9\u4E2D\u641C\u7D22\u7684\u5173\u952E\u8BCD" },
1802
+ startTimeMs: { type: "number", description: "\u5F00\u59CB\u65F6\u95F4\u6233(\u6BEB\u79D2)" },
1803
+ endTimeMs: { type: "number", description: "\u7ED3\u675F\u65F6\u95F4\u6233(\u6BEB\u79D2)" },
1804
+ pageSize: { type: "number", description: "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA420\uFF0C\u6700\u592750" },
1805
+ pageNum: { type: "number", description: "\u9875\u7801\uFF0C\u9ED8\u8BA41" }
1806
+ },
1807
+ required: []
1808
+ }
1809
+ },
1810
+ category: "platform",
1811
+ handler: async (args, ctx) => {
1812
+ const cache = flipDataSource.get(ctx.userId);
1813
+ if (!cache) return { total: 0, records: [] };
1814
+ let records = cache.records;
1815
+ const xoxId = args.xoxId;
1816
+ const keyword = args.keyword;
1817
+ const startTimeMs = args.startTimeMs;
1818
+ const endTimeMs = args.endTimeMs;
1819
+ const pageSize = Math.min(args.pageSize || 20, 50);
1820
+ const pageNum = args.pageNum || 1;
1821
+ if (xoxId) {
1822
+ records = records.filter((r) => {
1823
+ const base = r.baseUserInfo;
1824
+ return base?.userId === xoxId;
1825
+ });
1826
+ }
1827
+ if (keyword) {
1828
+ const kw = keyword.toLowerCase();
1829
+ records = records.filter(
1830
+ (r) => String(r.content || "").toLowerCase().includes(kw) || String(r.answerContent || "").toLowerCase().includes(kw)
1831
+ );
1832
+ }
1833
+ if (startTimeMs) {
1834
+ records = records.filter((r) => Number(r.qtime || 0) >= startTimeMs);
1835
+ }
1836
+ if (endTimeMs) {
1837
+ records = records.filter((r) => Number(r.qtime || 0) <= endTimeMs);
1838
+ }
1839
+ const start = (pageNum - 1) * pageSize;
1840
+ const page = records.slice(start, start + pageSize);
1841
+ return {
1842
+ total: records.length,
1843
+ pageNum,
1844
+ pageSize,
1845
+ records: page.map((r) => ({
1846
+ answerId: r.answerId,
1847
+ content: r.content,
1848
+ answerContent: r.answerContent,
1849
+ qtime: r.qtime,
1850
+ answerTime: r.answerTime,
1851
+ cost: r.cost,
1852
+ type: r.type,
1853
+ answerType: r.answerType,
1854
+ status: r.status,
1855
+ xoxId: r.baseUserInfo?.userId,
1856
+ xoxNickname: r.baseUserInfo?.nickname
1857
+ }))
1858
+ };
1859
+ }
1860
+ },
1861
+ {
1862
+ type: "function",
1863
+ function: {
1864
+ name: "\u5E73\u53F0.getXoxList",
1865
+ description: "\u83B7\u53D6\u6240\u6709\u6709\u7FFB\u724C\u8BB0\u5F55\u7684\u5076\u50CF\u5217\u8868\uFF0C\u8FD4\u56DE\u6BCF\u4E2A\u5076\u50CF\u7684ID\u3001\u6635\u79F0\u548C\u7FFB\u724C\u6570\u91CF",
1866
+ parameters: { type: "object", properties: {}, required: [] }
1867
+ },
1868
+ category: "platform",
1869
+ handler: async (_args, ctx) => {
1870
+ const cache = flipDataSource.get(ctx.userId);
1871
+ if (!cache) return [];
1872
+ const map = /* @__PURE__ */ new Map();
1873
+ for (const r of cache.records) {
1874
+ const base = r.baseUserInfo;
1875
+ if (!base?.userId) continue;
1876
+ const existing = map.get(base.userId);
1877
+ if (existing) {
1878
+ existing.count++;
1879
+ } else {
1880
+ map.set(base.userId, { id: base.userId, nickname: base.nickname || String(base.userId), count: 1 });
1881
+ }
1882
+ }
1883
+ return Array.from(map.values()).sort((a, b) => b.count - a.count);
1884
+ }
1885
+ },
1886
+ {
1887
+ type: "function",
1888
+ function: {
1889
+ name: "\u5E73\u53F0.getDashboardInfo",
1890
+ description: "\u83B7\u53D6\u7FFB\u724C\u6570\u636E\u7684\u7EDF\u8BA1\u4EEA\u8868\u76D8\u4FE1\u606F\uFF0C\u5305\u62EC\u603B\u7FFB\u724C\u6570\u3001\u8FDB\u884C\u4E2D\u6570\u91CF\u3001\u5DF2\u9000\u56DE\u6570\u91CF\u3001\u603B\u82B1\u8D39\u7B49",
1891
+ parameters: { type: "object", properties: {}, required: [] }
1892
+ },
1893
+ category: "platform",
1894
+ handler: async (_args, ctx) => {
1895
+ const cache = flipDataSource.get(ctx.userId);
1896
+ return cache?.dashboardInfo || { totalFlipCount: 0, runningCount: 0, returnedCount: 0, costTotal: 0 };
1897
+ }
1898
+ },
1899
+ // ─── File System ───
1900
+ {
1901
+ type: "function",
1902
+ function: {
1903
+ name: "\u5E73\u53F0.readFile",
1904
+ description: "\u8BFB\u53D6\u672C\u5730\u6587\u4EF6\u5185\u5BB9\uFF0C\u652F\u6301\u6587\u672C\u6587\u4EF6\u3002\u8FD4\u56DE\u6587\u4EF6\u5185\u5BB9\u5B57\u7B26\u4E32\u3002",
1905
+ parameters: {
1906
+ type: "object",
1907
+ properties: {
1908
+ path: { type: "string", description: "\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84" },
1909
+ encoding: { type: "string", description: "\u7F16\u7801\uFF0C\u9ED8\u8BA4utf-8", enum: ["utf-8", "base64"] }
1910
+ },
1911
+ required: ["path"]
1912
+ }
1913
+ },
1914
+ category: "platform",
1915
+ handler: async (args) => {
1916
+ const filePath = args.path;
1917
+ const encoding = args.encoding || "utf-8";
1918
+ if (!import_node_fs3.default.existsSync(filePath)) return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
1919
+ return import_node_fs3.default.readFileSync(filePath, encoding);
1920
+ }
1921
+ },
1922
+ // ─── Utility ───
1923
+ {
1924
+ type: "function",
1925
+ function: {
1926
+ name: "\u5E73\u53F0.getCurrentTime",
1927
+ description: "\u83B7\u53D6\u5F53\u524D\u65F6\u95F4\u4FE1\u606F\uFF0C\u8FD4\u56DE\u65F6\u95F4\u6233\u3001ISO\u683C\u5F0F\u65F6\u95F4\u548C\u53EF\u8BFB\u683C\u5F0F",
1928
+ parameters: { type: "object", properties: {}, required: [] }
1929
+ },
1930
+ category: "platform",
1931
+ handler: async () => {
1932
+ const now = /* @__PURE__ */ new Date();
1933
+ return {
1934
+ timestamp: now.getTime(),
1935
+ iso: now.toISOString(),
1936
+ readable: now.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
1937
+ timezone: "Asia/Shanghai"
1938
+ };
1939
+ }
1940
+ },
1941
+ {
1942
+ type: "function",
1943
+ function: {
1944
+ name: "\u5E73\u53F0.processText",
1945
+ description: `\u5BF9\u6587\u672C\u8FDB\u884C\u57FA\u7840\u5904\u7406\uFF1A\u7EDF\u8BA1\u5B57\u6570\u3001\u63D0\u53D6\u5173\u952E\u8BCD\u3001\u622A\u65AD\u7B49\u3002
1946
+ \u64CD\u4F5C\u7C7B\u578B: count(\u7EDF\u8BA1)\u3001truncate(\u622A\u65AD)\u3001extract(\u63D0\u53D6\u5173\u952E\u8BCD)\u3002`,
1947
+ parameters: {
1948
+ type: "object",
1949
+ properties: {
1950
+ text: { type: "string", description: "\u8981\u5904\u7406\u7684\u6587\u672C" },
1951
+ operation: { type: "string", description: "\u64CD\u4F5C\u7C7B\u578B", enum: ["count", "truncate", "extract"] },
1952
+ maxLength: { type: "number", description: "\u622A\u65AD\u6700\u5927\u957F\u5EA6(\u4EC5truncate\u65F6\u4F7F\u7528)" }
1953
+ },
1954
+ required: ["text", "operation"]
1955
+ }
1956
+ },
1957
+ category: "platform",
1958
+ handler: async (args) => {
1959
+ const text = args.text;
1960
+ const op = args.operation;
1961
+ if (op === "count") {
1962
+ return { charCount: text.length, wordCount: text.replace(/\s/g, "").length };
1963
+ }
1964
+ if (op === "truncate") {
1965
+ const maxLen = args.maxLength || 200;
1966
+ return { truncated: text.length > maxLen, result: text.slice(0, maxLen) + (text.length > maxLen ? "..." : "") };
1967
+ }
1968
+ if (op === "extract") {
1969
+ const phrases = /* @__PURE__ */ new Map();
1970
+ for (let i = 0; i < text.length - 1; i++) {
1971
+ const phrase = text.slice(i, i + 2);
1972
+ if (/^[一-龥]{2}$/.test(phrase)) {
1973
+ phrases.set(phrase, (phrases.get(phrase) || 0) + 1);
1974
+ }
1975
+ }
1976
+ return Array.from(phrases.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([k, v]) => ({ phrase: k, count: v }));
1977
+ }
1978
+ return { error: `\u672A\u77E5\u64CD\u4F5C: ${op}` };
1979
+ }
1980
+ }
1981
+ ];
1982
+ }
1983
+
1984
+ // src/tools/mirror-tools.ts
1985
+ function createMirrorTools(mirrorStore) {
1986
+ return [
1987
+ {
1988
+ type: "function",
1989
+ function: {
1990
+ name: "mirror.getProfile",
1991
+ description: "\u83B7\u53D6\u6307\u5B9A\u5076\u50CF\u7684\u6570\u5B57\u955C\u50CF\u6863\u6848\uFF0C\u5305\u542B\u6027\u683C\u7279\u5F81\u3001\u8BF4\u8BDD\u98CE\u683C\u3001\u53E3\u5934\u7985\u3001\u60C5\u611F\u57FA\u8C03\u3001\u753B\u50CF\u63CF\u8FF0\u7B49",
1992
+ parameters: {
1993
+ type: "object",
1994
+ properties: {
1995
+ xoxId: { type: "number", description: "\u5076\u50CFID" }
1996
+ },
1997
+ required: ["xoxId"]
1998
+ }
1999
+ },
2000
+ category: "mirror",
2001
+ handler: async (args, ctx) => {
2002
+ const xoxId = args.xoxId;
2003
+ const profile = mirrorStore.getProfile(xoxId, ctx.userId);
2004
+ if (!profile) return { error: `\u672A\u627E\u5230\u5076\u50CF ${xoxId} \u7684\u955C\u50CF\u6863\u6848\uFF0C\u8BF7\u5148\u751F\u6210\u955C\u50CF` };
2005
+ return {
2006
+ xoxNickname: profile.xoxNickname,
2007
+ personalityTraits: profile.persona.personalityTraits,
2008
+ speechPatterns: profile.persona.speechPatterns,
2009
+ signaturePhrases: profile.persona.signaturePhrases,
2010
+ emotionalTone: profile.persona.emotionalTone,
2011
+ personaSummary: profile.persona.personaSummary,
2012
+ maturityScore: profile.meta.maturityScore,
2013
+ totalSamplesUsed: profile.meta.totalSamplesUsed
2014
+ };
2015
+ }
2016
+ },
2017
+ {
2018
+ type: "function",
2019
+ function: {
2020
+ name: "mirror.listProfiles",
2021
+ description: "\u5217\u51FA\u6240\u6709\u5DF2\u751F\u6210\u7684\u6570\u5B57\u955C\u50CF\u5217\u8868",
2022
+ parameters: {
2023
+ type: "object",
2024
+ properties: {},
2025
+ required: []
2026
+ }
2027
+ },
2028
+ category: "mirror",
2029
+ handler: async (_args, ctx) => {
2030
+ const profiles = mirrorStore.listProfiles(ctx.userId);
2031
+ return profiles.map((p) => ({
2032
+ xoxId: p.xoxId,
2033
+ xoxNickname: p.xoxNickname,
2034
+ maturityScore: p.meta.maturityScore,
2035
+ totalSamplesUsed: p.meta.totalSamplesUsed,
2036
+ lastUpdatedAt: p.meta.lastUpdatedAt
2037
+ }));
2038
+ }
2039
+ },
2040
+ {
2041
+ type: "function",
2042
+ function: {
2043
+ name: "mirror.getFewShotExamples",
2044
+ description: "\u83B7\u53D6\u5076\u50CF\u7684 few-shot \u4EA4\u4E92\u793A\u4F8B\uFF0C\u7528\u4E8E\u6A21\u4EFF\u5176\u56DE\u590D\u98CE\u683C",
2045
+ parameters: {
2046
+ type: "object",
2047
+ properties: {
2048
+ xoxId: { type: "number", description: "\u5076\u50CFID" },
2049
+ count: { type: "number", description: "\u8FD4\u56DE\u793A\u4F8B\u6570\u91CF\uFF0C\u9ED8\u8BA43" }
2050
+ },
2051
+ required: ["xoxId"]
2052
+ }
2053
+ },
2054
+ category: "mirror",
2055
+ handler: async (args, ctx) => {
2056
+ const xoxId = args.xoxId;
2057
+ const count = args.count || 3;
2058
+ const profile = mirrorStore.getProfile(xoxId, ctx.userId);
2059
+ if (!profile) return { error: `\u672A\u627E\u5230\u5076\u50CF ${xoxId} \u7684\u955C\u50CF\u6863\u6848` };
2060
+ return profile.fewShotExamples.slice(0, count);
2061
+ }
2062
+ }
2063
+ ];
2064
+ }
2065
+
2066
+ // src/pocket/user.ts
2067
+ var user_exports = {};
2068
+ __export(user_exports, {
2069
+ getUnreadMessageNum: () => getUnreadMessageNum,
2070
+ loginWithCode: () => loginWithCode,
2071
+ loginWithToken: () => loginWithToken,
2072
+ normalizePocketUser: () => normalizePocketUser,
2073
+ sendVerificationCode: () => sendVerificationCode
2074
+ });
2075
+ async function sendVerificationCode(client, phone) {
2076
+ await client.requestBeforeLogin("/user/api/v2/sms/send_sms", {
2077
+ mobile: phone,
2078
+ area: "86",
2079
+ businessCode: 1,
2080
+ deviceToken: ""
2081
+ });
2082
+ }
2083
+ async function loginWithCode(client, phone, code) {
2084
+ const res = await client.requestBeforeLogin(
2085
+ "/user/api/v2/login/app/app_login",
2086
+ {
2087
+ deviceToken: "",
2088
+ loginType: "MOBILE_SMS_CODE",
2089
+ mobileCodeLogin: {
2090
+ area: "86",
2091
+ mobile: phone,
2092
+ code
2093
+ }
2094
+ }
2095
+ );
2096
+ if (res.success === false || res.status != null && res.status !== 200) {
2097
+ throw new Error(res.message || "\u767B\u5F55\u5931\u8D25");
2098
+ }
2099
+ return res.content ?? {};
2100
+ }
2101
+ async function getUnreadMessageNum(client, token) {
2102
+ return client.request(
2103
+ "/message/api/v1/unread/message/num",
2104
+ {},
2105
+ token,
2106
+ true
2107
+ );
2108
+ }
2109
+ async function loginWithToken(client, token) {
2110
+ const res = await client.request(
2111
+ "/user/api/v1/user/info/reload",
2112
+ { from: "appstart" },
2113
+ token,
2114
+ true
2115
+ );
2116
+ if (res.success === false) return null;
2117
+ const content = res.data?.content ?? res.content;
2118
+ if (!content || typeof content !== "object") return null;
2119
+ return { ...content, ...content.userInfo };
2120
+ }
2121
+ function normalizePocketUser(raw, token, phone = "") {
2122
+ const nested = raw.userInfo || raw;
2123
+ return {
2124
+ userId: Number(nested.userId ?? raw.userId ?? 0) || void 0,
2125
+ nickname: String(nested.nickname ?? raw.nickname ?? ""),
2126
+ name: String(nested.nickname ?? raw.nickname ?? raw.name ?? ""),
2127
+ phone: String(phone || nested.mobile || raw.phone || ""),
2128
+ avatar: String(nested.avatar ?? ""),
2129
+ level: Number(nested.level ?? 0) || void 0,
2130
+ money: Number(nested.money ?? 0) || void 0,
2131
+ token: String(token),
2132
+ lastLoginTime: Date.now()
2133
+ };
2134
+ }
2135
+
2136
+ // src/pocket/flip.ts
2137
+ var flip_exports = {};
2138
+ __export(flip_exports, {
2139
+ getAllFlips: () => getAllFlips,
2140
+ getDataSourcePage: () => getDataSourcePage,
2141
+ getMemberFlipHistory: () => getMemberFlipHistory,
2142
+ sendFlip: () => sendFlip,
2143
+ syncFlips: () => syncFlips
2144
+ });
2145
+ function groupDataById(allUserData) {
2146
+ const groupData = {};
2147
+ allUserData.forEach((item) => {
2148
+ const xoxId = item?.baseUserInfo?.userId;
2149
+ if (!xoxId) return;
2150
+ const cardInfo = {
2151
+ answerId: item.answerId,
2152
+ cost: item.cost,
2153
+ answerType: item.answerType,
2154
+ answerTime: item.answerTime,
2155
+ qtime: item.qtime,
2156
+ type: item.type
2157
+ };
2158
+ const key = String(xoxId);
2159
+ if (key in groupData) {
2160
+ groupData[key].cards.push(cardInfo);
2161
+ } else {
2162
+ groupData[key] = {
2163
+ xoxId,
2164
+ xoxNickname: item.baseUserInfo?.nickname ?? "",
2165
+ cards: [cardInfo]
2166
+ };
2167
+ }
2168
+ });
2169
+ return groupData;
2170
+ }
2171
+ function recordKey(r) {
2172
+ if (r?.questionId) return `q:${r.questionId}`;
2173
+ if (r?.answerId) return `a:${r.answerId}`;
2174
+ return `f:${r?.qtime ?? ""}|${r?.content ?? ""}`;
2175
+ }
2176
+ function calcDashboard(allUserData) {
2177
+ const info = {
2178
+ totalFlipCount: allUserData.length,
2179
+ runningCount: 0,
2180
+ returnedCount: 0,
2181
+ costTotal: 0
2182
+ };
2183
+ allUserData.forEach((card) => {
2184
+ if (card.status === 3) info.returnedCount += 1;
2185
+ else if (card.status === 1) info.runningCount += 1;
2186
+ else if (card.status === 2) info.costTotal += card.cost;
2187
+ });
2188
+ return info;
2189
+ }
2190
+ async function syncFlips(client, token, userId, flipDataSource, logger, mode = "incremental", onProgress) {
2191
+ if (mode === "full") {
2192
+ flipDataSource.clear(userId);
2193
+ }
2194
+ const cache = flipDataSource.get(userId);
2195
+ const existingRecords = mode === "incremental" ? cache?.records ?? [] : [];
2196
+ const existingIds = new Set(existingRecords.map((r) => recordKey(r)));
2197
+ const existingIndex = new Map(existingRecords.map((r, i) => [recordKey(r), i]));
2198
+ let newRecords = [];
2199
+ let beginLimit = 0;
2200
+ let responseSize = 20;
2201
+ let stopT = 0;
2202
+ const groupNum = 40;
2203
+ while (responseSize > 0) {
2204
+ const response = await client.request(
2205
+ "/idolanswer/api/idolanswer/v1/user/question/list",
2206
+ { status: 0, beginLimit, memberId: "", limit: 20 },
2207
+ token,
2208
+ true
2209
+ );
2210
+ let sleepTime = 10;
2211
+ if (beginLimit - stopT * groupNum * responseSize > 100) {
2212
+ sleepTime = 6e4;
2213
+ stopT += 1;
2214
+ }
2215
+ if (response.status === 200) {
2216
+ const userData = response.content ?? [];
2217
+ if (mode === "incremental" && existingIds.size > 0) {
2218
+ let hitExisting = false;
2219
+ for (const item of userData) {
2220
+ const key = recordKey(item);
2221
+ if (existingIds.has(key)) {
2222
+ const idx = existingIndex.get(key);
2223
+ if (idx !== void 0) existingRecords[idx] = item;
2224
+ hitExisting = true;
2225
+ break;
2226
+ }
2227
+ newRecords.push(item);
2228
+ }
2229
+ if (hitExisting) {
2230
+ responseSize = 0;
2231
+ } else {
2232
+ responseSize = userData.length;
2233
+ beginLimit += userData.length;
2234
+ }
2235
+ } else {
2236
+ newRecords = newRecords.concat(userData);
2237
+ responseSize = userData.length;
2238
+ beginLimit += userData.length;
2239
+ }
2240
+ onProgress?.(newRecords.length);
2241
+ if (responseSize > 0) {
2242
+ await new Promise((r) => setTimeout(r, sleepTime));
2243
+ }
2244
+ } else {
2245
+ logger.warn(`\u83B7\u53D6\u7FFB\u724C\u6570\u636E\u5931\u8D25: ${response.message}`);
2246
+ break;
2247
+ }
2248
+ }
2249
+ const merged = mode === "full" ? newRecords : [...newRecords, ...existingRecords];
2250
+ const seen = /* @__PURE__ */ new Set();
2251
+ const allUserData = merged.filter((item) => {
2252
+ const key = recordKey(item);
2253
+ if (seen.has(key)) return false;
2254
+ seen.add(key);
2255
+ return true;
2256
+ });
2257
+ if (!allUserData.length && !newRecords.length) {
2258
+ return { allUserData: existingRecords, dashboardInfo: cache?.dashboardInfo ?? null };
2259
+ }
2260
+ const dashboardInfo = calcDashboard(allUserData);
2261
+ const groupedData = groupDataById(allUserData);
2262
+ const nextCache = {
2263
+ userId,
2264
+ records: allUserData,
2265
+ groupedDataById: groupedData,
2266
+ dashboardInfo,
2267
+ meta: { syncedAt: Date.now(), recordCount: allUserData.length }
2268
+ };
2269
+ flipDataSource.set(nextCache);
2270
+ logger.info(`syncFlips: ${newRecords.length} new, total ${allUserData.length}`);
2271
+ return { allUserData, dashboardInfo };
2272
+ }
2273
+ async function getAllFlips(client, token, userId, flipDataSource, logger) {
2274
+ return syncFlips(client, token, userId, flipDataSource, logger, "full");
2275
+ }
2276
+ async function getDataSourcePage(searchParams, flipDataSource, userId) {
2277
+ const { xoxId, startTimeMs, endTimeMs, type, status, answerType, keyword } = searchParams;
2278
+ const cache = flipDataSource.get(userId);
2279
+ const allUserData = cache?.records ?? [];
2280
+ const filtered = allUserData.filter((item) => {
2281
+ if (xoxId != null && `${item?.baseUserInfo?.userId}` !== `${xoxId}`) return false;
2282
+ if (startTimeMs && Number(item?.qtime || Infinity) < Number(startTimeMs)) return false;
2283
+ if (endTimeMs && Number(item?.qtime || 0) > Number(endTimeMs)) return false;
2284
+ if (type != null && `${item?.type}` !== `${type}`) return false;
2285
+ if (status != null && `${item?.status}` !== `${status}`) return false;
2286
+ if (answerType != null && `${item?.answerType}` !== `${answerType}`) return false;
2287
+ if (keyword && !item?.answerContent?.includes(keyword) && !item?.content?.includes(keyword)) return false;
2288
+ return true;
2289
+ });
2290
+ return { data: filtered, total: filtered.length };
2291
+ }
2292
+ async function sendFlip(client, params, token) {
2293
+ const res = await client.request(
2294
+ "/idolanswer/api/idolanswer/v1/user/question",
2295
+ params,
2296
+ token,
2297
+ true
2298
+ );
2299
+ return !!res.content;
2300
+ }
2301
+ async function getMemberFlipHistory(client, token, memberId, beginLimit = 10, limit = 10) {
2302
+ const res = await client.request(
2303
+ "/idolanswer/api/idolanswer/v1/user/question/list",
2304
+ { status: 0, memberId: String(memberId), beginLimit, limit },
2305
+ token,
2306
+ true
2307
+ );
2308
+ const content = res?.content;
2309
+ let records = [];
2310
+ if (Array.isArray(content)) {
2311
+ records = content;
2312
+ } else if (content && typeof content === "object") {
2313
+ records = content.flipCompleteList ?? content.answerList ?? content.list ?? [];
2314
+ }
2315
+ return { records, hasMore: records.length >= limit };
2316
+ }
2317
+
2318
+ // src/pocket/member.ts
2319
+ var member_exports = {};
2320
+ __export(member_exports, {
2321
+ fetchMemberList: () => fetchMemberList,
2322
+ getMemberFlipPriceInfo: () => getMemberFlipPriceInfo,
2323
+ getMemberListByTab: () => getMemberListByTab,
2324
+ getTeamListInfo: () => getTeamListInfo
2325
+ });
2326
+ async function getTeamListInfo(client, token) {
2327
+ const res = await client.request(
2328
+ "/im/api/v1/im/team/tab/list",
2329
+ { groupId: 0, typeId: 0, ctime: 0, limit: 20 },
2330
+ token,
2331
+ true
2332
+ );
2333
+ return res.content;
2334
+ }
2335
+ async function getMemberListByTab(client, token, tabId) {
2336
+ const res = await client.request(
2337
+ "/im/api/v1/im/team/server/list",
2338
+ { tabId },
2339
+ token,
2340
+ true
2341
+ );
2342
+ return res.content;
2343
+ }
2344
+ async function fetchMemberList(client, token) {
2345
+ const teamData = await getTeamListInfo(client, token);
2346
+ if (!teamData?.serverTabList) return [];
2347
+ const list = [];
2348
+ for (const tab of teamData.serverTabList) {
2349
+ const serverData = await getMemberListByTab(client, token, tab.tabId);
2350
+ if (!serverData?.serverApiList) continue;
2351
+ for (const item of serverData.serverApiList) {
2352
+ list.push({
2353
+ teamName: tab.tabName,
2354
+ teamId: Number(item.teamId),
2355
+ serverIcon: String(item.serverDefaultIcon ?? ""),
2356
+ serverId: Number(item.serverId),
2357
+ serverName: String(item.serverName ?? ""),
2358
+ memberId: Number(item.serverOwner),
2359
+ memberName: String(item.serverDefaultName ?? "")
2360
+ });
2361
+ }
2362
+ }
2363
+ return list;
2364
+ }
2365
+ async function getMemberFlipPriceInfo(client, token, memberId) {
2366
+ const res = await client.request(
2367
+ "/idolanswer/api/idolanswer/v2/custom/index",
2368
+ { memberId: Number(memberId) },
2369
+ token,
2370
+ true
2371
+ );
2372
+ return res.content;
2373
+ }
2374
+
2375
+ // src/pocket/room.ts
2376
+ var room_exports = {};
2377
+ __export(room_exports, {
2378
+ getRoomChannelByStar: () => getRoomChannelByStar,
2379
+ getRoomInfo: () => getRoomInfo,
2380
+ getRoomMessages: () => getRoomMessages
2381
+ });
2382
+ async function getRoomChannelByStar(client, token, starId) {
2383
+ const res = await client.request("/im/api/v1/im/server/jump", { starId, tabId: 0, targetType: 1 }, token, true);
2384
+ const c = res.content;
2385
+ const channelId = c?.channelId ?? c?.channelInfo?.channelId ?? 0;
2386
+ const serverId = c?.serverId ?? c?.channelInfo?.serverId;
2387
+ return {
2388
+ channelId: Number(channelId),
2389
+ serverId: serverId != null ? Number(serverId) : void 0
2390
+ };
2391
+ }
2392
+ async function getRoomInfo(client, token, channelId) {
2393
+ const res = await client.request(
2394
+ "/im/api/v1/im/team/room/info",
2395
+ { channelId: String(channelId) },
2396
+ token,
2397
+ true
2398
+ );
2399
+ return res.content?.channelInfo ?? null;
2400
+ }
2401
+ function parseMessage(raw) {
2402
+ let text;
2403
+ let mediaUrl;
2404
+ let mediaDuration;
2405
+ let liveInfo;
2406
+ let giftInfo;
2407
+ let replyInfo;
2408
+ if (raw.msgType === "TEXT") {
2409
+ text = raw.bodys;
2410
+ } else {
2411
+ try {
2412
+ const b = JSON.parse(raw.bodys);
2413
+ if (raw.msgType === "IMAGE" || raw.msgType === "VOICE" || raw.msgType === "VIDEO") {
2414
+ mediaUrl = b.url;
2415
+ if (raw.msgType === "VOICE" && b.dur) mediaDuration = Number(b.dur);
2416
+ } else if (raw.msgType === "LIVEPUSH") {
2417
+ const live = b.livePushInfo ?? b;
2418
+ liveInfo = {
2419
+ liveId: live.liveId,
2420
+ liveTitle: live.liveTitle,
2421
+ liveCover: live.liveCover,
2422
+ shortPath: live.shortPath
2423
+ };
2424
+ } else if (raw.msgType === "GIFT_TEXT") {
2425
+ const g = b.giftInfo ?? b;
2426
+ giftInfo = {
2427
+ giftName: g.giftName,
2428
+ giftNum: Number(g.giftNum ?? g.giftCount ?? 1),
2429
+ giftPic: g.giftPic
2430
+ };
2431
+ } else if (raw.msgType === "REPLY") {
2432
+ const r = b.replyInfo ?? b;
2433
+ replyInfo = {
2434
+ replyName: r.replyName,
2435
+ replyText: r.replyText,
2436
+ replyMessageId: r.replyMessageId
2437
+ };
2438
+ text = r.text ?? raw.bodys;
2439
+ } else {
2440
+ text = raw.bodys;
2441
+ }
2442
+ } catch {
2443
+ text = raw.bodys;
2444
+ }
2445
+ }
2446
+ let sender;
2447
+ if (raw.extInfo) {
2448
+ try {
2449
+ const e = JSON.parse(raw.extInfo);
2450
+ const u = e.user;
2451
+ if (u) {
2452
+ sender = {
2453
+ userId: u.userId,
2454
+ nickName: u.nickName || "",
2455
+ avatar: u.avatar || "",
2456
+ level: u.level
2457
+ };
2458
+ }
2459
+ } catch {
2460
+ }
2461
+ }
2462
+ return {
2463
+ msgId: String(raw.msgIdServer || raw.msgIdClient || raw.msgId || raw.msgTime),
2464
+ msgTime: raw.msgTime,
2465
+ msgType: raw.msgType,
2466
+ text,
2467
+ mediaUrl,
2468
+ mediaDuration,
2469
+ liveInfo,
2470
+ giftInfo,
2471
+ replyInfo,
2472
+ sender
2473
+ };
2474
+ }
2475
+ async function getRoomMessages(client, token, serverId, channelId, nextTime) {
2476
+ const res = await client.request(
2477
+ "/im/api/v1/team/message/list/all",
2478
+ { limit: 100, serverId, channelId, nextTime },
2479
+ token,
2480
+ true
2481
+ );
2482
+ const content = res?.content ?? {};
2483
+ const rawList = content?.message ?? content?.messageList ?? content?.list ?? content?.messages ?? content?.data ?? (Array.isArray(content) ? content : []);
2484
+ const next = content?.nextTime ?? 0;
2485
+ const messages = rawList.filter((m) => m && m.msgTime).map(parseMessage).sort((a, b) => a.msgTime - b.msgTime);
2486
+ return { messages, nextTime: Number(next) };
2487
+ }
2488
+
2489
+ // src/tools/pocket-tools.ts
2490
+ function getToken(configStore) {
2491
+ const stored = configStore.get("pocket_token");
2492
+ if (stored) return stored;
2493
+ return process.env.POCKET_TOKEN;
2494
+ }
2495
+ function createPocketTools(params) {
2496
+ const { pocketClient, configStore, flipDataSource, logger } = params;
2497
+ const userId = "mcp";
2498
+ return [
2499
+ {
2500
+ type: "function",
2501
+ function: {
2502
+ name: "pocket.sendVerificationCode",
2503
+ description: "\u53D1\u9001\u77ED\u4FE1\u9A8C\u8BC1\u7801\u5230\u6307\u5B9A\u624B\u673A\u53F7\uFF0C\u7528\u4E8E\u540E\u7EED\u9A8C\u8BC1\u7801\u767B\u5F55",
2504
+ parameters: {
2505
+ type: "object",
2506
+ properties: {
2507
+ phone: { type: "string", description: "\u624B\u673A\u53F7\u7801\uFF0811\u4F4D\uFF09" }
2508
+ },
2509
+ required: ["phone"]
2510
+ }
2511
+ },
2512
+ category: "pocket",
2513
+ handler: async (args) => {
2514
+ const phone = args.phone;
2515
+ if (!phone || !/^\d{11}$/.test(phone)) {
2516
+ return { error: "\u8BF7\u8F93\u5165\u6B63\u786E\u768411\u4F4D\u624B\u673A\u53F7" };
2517
+ }
2518
+ try {
2519
+ await sendVerificationCode(pocketClient, phone);
2520
+ return { success: true, message: `\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\u5230 ${phone}` };
2521
+ } catch (e) {
2522
+ return { error: `\u53D1\u9001\u5931\u8D25: ${String(e)}` };
2523
+ }
2524
+ }
2525
+ },
2526
+ {
2527
+ type: "function",
2528
+ function: {
2529
+ name: "pocket.loginWithCode",
2530
+ description: "\u4F7F\u7528\u77ED\u4FE1\u9A8C\u8BC1\u7801\u767B\u5F55\uFF0C\u6210\u529F\u540E\u5C06 token \u6301\u4E45\u5316\u4FDD\u5B58\uFF0C\u540E\u7EED\u5DE5\u5177\u81EA\u52A8\u643A\u5E26",
2531
+ parameters: {
2532
+ type: "object",
2533
+ properties: {
2534
+ phone: { type: "string", description: "\u624B\u673A\u53F7\u7801\uFF0811\u4F4D\uFF09" },
2535
+ code: { type: "string", description: "\u77ED\u4FE1\u9A8C\u8BC1\u7801" }
2536
+ },
2537
+ required: ["phone", "code"]
2538
+ }
2539
+ },
2540
+ category: "pocket",
2541
+ handler: async (args) => {
2542
+ const phone = args.phone;
2543
+ const code = args.code;
2544
+ try {
2545
+ const content = await loginWithCode(pocketClient, phone, code);
2546
+ const token = content?.token;
2547
+ if (!token) {
2548
+ return { error: "\u767B\u5F55\u5931\u8D25\uFF1A\u54CD\u5E94\u4E2D\u672A\u5305\u542B token", raw: content };
2549
+ }
2550
+ configStore.set("pocket_token", token);
2551
+ const userInfo = content?.userInfo;
2552
+ logger.info(`[pocket-tools] loginWithCode success, userId=${userInfo?.userId ?? "?"}`);
2553
+ return {
2554
+ success: true,
2555
+ userId: userInfo?.userId,
2556
+ nickname: userInfo?.nickname,
2557
+ message: "\u767B\u5F55\u6210\u529F\uFF0Ctoken \u5DF2\u4FDD\u5B58"
2558
+ };
2559
+ } catch (e) {
2560
+ return { error: `\u767B\u5F55\u5931\u8D25: ${String(e)}` };
2561
+ }
2562
+ }
2563
+ },
2564
+ {
2565
+ type: "function",
2566
+ function: {
2567
+ name: "pocket.loginWithToken",
2568
+ description: `\u4F7F\u7528\u5DF2\u6709 Token \u9A8C\u8BC1\u5E76\u767B\u5F55\u3002\u4E0D\u4F20 token \u53C2\u6570\u65F6\u81EA\u52A8\u5C1D\u8BD5\u73AF\u5883\u53D8\u91CF POCKET_TOKEN\u3002
2569
+ \u5982\u679C\u4F60\u5DF2\u6709 token\uFF08\u6BD4\u5982\u4ECE\u6D4F\u89C8\u5668\u6293\u53D6\uFF09\uFF0C\u76F4\u63A5\u7528\u6B64\u5DE5\u5177\u767B\u5F55\uFF0C\u65E0\u9700\u9A8C\u8BC1\u7801\u3002`,
2570
+ parameters: {
2571
+ type: "object",
2572
+ properties: {
2573
+ token: { type: "string", description: "Pocket48 \u767B\u5F55 Token\u3002\u4E0D\u4F20\u5219\u4ECE\u73AF\u5883\u53D8\u91CF POCKET_TOKEN \u8BFB\u53D6" }
2574
+ },
2575
+ required: []
2576
+ }
2577
+ },
2578
+ category: "pocket",
2579
+ handler: async (args) => {
2580
+ const token = args.token || getToken(configStore);
2581
+ if (!token) {
2582
+ return { error: "\u672A\u63D0\u4F9B token\u3002\u8BF7\u4F20\u5165 token \u53C2\u6570\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF POCKET_TOKEN" };
2583
+ }
2584
+ try {
2585
+ const user = await loginWithToken(pocketClient, token);
2586
+ if (!user) {
2587
+ return { error: "Token \u65E0\u6548\u6216\u5DF2\u8FC7\u671F" };
2588
+ }
2589
+ configStore.set("pocket_token", token);
2590
+ logger.info(`[pocket-tools] loginWithToken success, userId=${user.userId}`);
2591
+ return {
2592
+ success: true,
2593
+ userId: user.userId,
2594
+ nickname: user.nickname,
2595
+ name: user.name,
2596
+ phone: user.phone,
2597
+ level: user.level,
2598
+ money: user.money,
2599
+ avatar: user.avatar,
2600
+ message: "\u767B\u5F55\u6210\u529F\uFF0Ctoken \u5DF2\u4FDD\u5B58"
2601
+ };
2602
+ } catch (e) {
2603
+ return { error: `Token \u9A8C\u8BC1\u5931\u8D25: ${String(e)}` };
2604
+ }
2605
+ }
2606
+ },
2607
+ // ── 用户类 ──
2608
+ {
2609
+ type: "function",
2610
+ function: {
2611
+ name: "pocket.getUserInfo",
2612
+ description: "\u83B7\u53D6\u5F53\u524D\u767B\u5F55\u7528\u6237\u7684\u57FA\u672C\u4FE1\u606F\uFF1A\u6635\u79F0\u3001\u7B49\u7EA7\u3001\u4F59\u989D\u3001\u5934\u50CF\u3001\u624B\u673A\u53F7",
2613
+ parameters: {
2614
+ type: "object",
2615
+ properties: {},
2616
+ required: []
2617
+ }
2618
+ },
2619
+ category: "pocket",
2620
+ handler: async () => {
2621
+ const token = getToken(configStore);
2622
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2623
+ try {
2624
+ const user = await loginWithToken(pocketClient, token);
2625
+ if (!user) return { error: "Token \u65E0\u6548\u6216\u5DF2\u8FC7\u671F" };
2626
+ return {
2627
+ userId: user.userId,
2628
+ nickname: user.nickname,
2629
+ name: user.name,
2630
+ phone: user.phone,
2631
+ level: user.level,
2632
+ money: user.money,
2633
+ avatar: user.avatar,
2634
+ lastLoginTime: user.lastLoginTime
2635
+ };
2636
+ } catch (e) {
2637
+ return { error: `\u83B7\u53D6\u7528\u6237\u4FE1\u606F\u5931\u8D25: ${String(e)}` };
2638
+ }
2639
+ }
2640
+ },
2641
+ {
2642
+ type: "function",
2643
+ function: {
2644
+ name: "pocket.getUnreadMessageNum",
2645
+ description: "\u83B7\u53D6\u672A\u8BFB\u6D88\u606F\u6570\u91CF\uFF0C\u5305\u62EC\u901A\u77E5\u3001\u7528\u6237\u6D88\u606F\u3001@\u6211\u3001\u8BC4\u8BBA\u7B49",
2646
+ parameters: {
2647
+ type: "object",
2648
+ properties: {},
2649
+ required: []
2650
+ }
2651
+ },
2652
+ category: "pocket",
2653
+ handler: async () => {
2654
+ const token = getToken(configStore);
2655
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2656
+ try {
2657
+ const res = await getUnreadMessageNum(pocketClient, token);
2658
+ return res.content ?? { notice: 0, user: 0, atme: 0, comment: 0 };
2659
+ } catch (e) {
2660
+ return { error: `\u83B7\u53D6\u672A\u8BFB\u6D88\u606F\u6570\u5931\u8D25: ${String(e)}` };
2661
+ }
2662
+ }
2663
+ },
2664
+ // ── 成员类 ──
2665
+ {
2666
+ type: "function",
2667
+ function: {
2668
+ name: "pocket.fetchMemberList",
2669
+ description: `\u83B7\u53D6\u6240\u6709\u53EF\u8BBF\u95EE\u7684\u961F\u4F0D\u548C\u6210\u5458\u5217\u8868\u3002\u8FD4\u56DE\u6BCF\u4E2A\u6210\u5458\u7684 teamName(\u961F\u4F0D\u540D)\u3001memberId\u3001memberName(\u6210\u5458\u540D)\u3001serverId \u7B49\u3002
2670
+ \u53EF\u4EE5\u7528 teamName \u53C2\u6570\u8FC7\u6EE4\u7279\u5B9A\u961F\u4F0D\uFF08\u5982 "SNH48"\u3001"BEJ48"\u3001"GNZ48"\u3001"CKG48"\uFF09\u3002`,
2671
+ parameters: {
2672
+ type: "object",
2673
+ properties: {
2674
+ teamName: { type: "string", description: "\u961F\u4F0D\u540D\u79F0\u8FC7\u6EE4\uFF0C\u5982 SNH48\u3001BEJ48\u3001GNZ48\u3002\u4E0D\u4F20\u5219\u8FD4\u56DE\u5168\u90E8" }
2675
+ },
2676
+ required: []
2677
+ }
2678
+ },
2679
+ category: "pocket",
2680
+ handler: async (args) => {
2681
+ const token = getToken(configStore);
2682
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2683
+ try {
2684
+ const list = await fetchMemberList(pocketClient, token);
2685
+ let result = list;
2686
+ const teamName = args.teamName;
2687
+ if (teamName) {
2688
+ result = list.filter((m) => m.teamName.includes(teamName));
2689
+ }
2690
+ return {
2691
+ total: result.length,
2692
+ members: result.map((m) => ({
2693
+ teamName: m.teamName,
2694
+ teamId: m.teamId,
2695
+ memberId: m.memberId,
2696
+ memberName: m.memberName,
2697
+ serverId: m.serverId,
2698
+ serverName: m.serverName
2699
+ }))
2700
+ };
2701
+ } catch (e) {
2702
+ return { error: `\u83B7\u53D6\u6210\u5458\u5217\u8868\u5931\u8D25: ${String(e)}` };
2703
+ }
2704
+ }
2705
+ },
2706
+ {
2707
+ type: "function",
2708
+ function: {
2709
+ name: "pocket.searchMembers",
2710
+ description: "\u6309\u6210\u5458\u540D\u79F0\u6A21\u7CCA\u641C\u7D22\u6210\u5458\u3002\u8F93\u5165\u90E8\u5206\u540D\u5B57\u5373\u53EF\u5339\u914D\uFF0C\u652F\u6301\u641C\u7D22\u6240\u6709\u961F\u4F0D",
2711
+ parameters: {
2712
+ type: "object",
2713
+ properties: {
2714
+ keyword: { type: "string", description: "\u6210\u5458\u540D\u79F0\u5173\u952E\u8BCD\uFF08\u652F\u6301\u90E8\u5206\u5339\u914D\uFF09" }
2715
+ },
2716
+ required: ["keyword"]
2717
+ }
2718
+ },
2719
+ category: "pocket",
2720
+ handler: async (args) => {
2721
+ const token = getToken(configStore);
2722
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2723
+ const keyword = args.keyword.toLowerCase();
2724
+ if (!keyword) return { error: "\u8BF7\u8F93\u5165\u641C\u7D22\u5173\u952E\u8BCD" };
2725
+ try {
2726
+ const list = await fetchMemberList(pocketClient, token);
2727
+ const matched = list.filter((m) => m.memberName.toLowerCase().includes(keyword));
2728
+ return {
2729
+ total: matched.length,
2730
+ keyword,
2731
+ members: matched.map((m) => ({
2732
+ teamName: m.teamName,
2733
+ memberId: m.memberId,
2734
+ memberName: m.memberName,
2735
+ serverId: m.serverId,
2736
+ serverName: m.serverName
2737
+ }))
2738
+ };
2739
+ } catch (e) {
2740
+ return { error: `\u641C\u7D22\u6210\u5458\u5931\u8D25: ${String(e)}` };
2741
+ }
2742
+ }
2743
+ },
2744
+ {
2745
+ type: "function",
2746
+ function: {
2747
+ name: "pocket.getMemberFlipPriceInfo",
2748
+ description: "\u67E5\u8BE2\u6307\u5B9A\u6210\u5458\u7684\u7FFB\u724C\u4EF7\u683C\u4FE1\u606F\uFF0C\u5305\u62EC\u533F\u540D\u7FFB\u724C\u4EF7\u683C\u3001\u666E\u901A\u7FFB\u724C\u4EF7\u683C\u3001\u79C1\u5BC6\u7FFB\u724C\u4EF7\u683C\u7B49",
2749
+ parameters: {
2750
+ type: "object",
2751
+ properties: {
2752
+ memberId: { type: "number", description: "\u6210\u5458ID (memberId)" }
2753
+ },
2754
+ required: ["memberId"]
2755
+ }
2756
+ },
2757
+ category: "pocket",
2758
+ handler: async (args) => {
2759
+ const token = getToken(configStore);
2760
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2761
+ try {
2762
+ const info = await getMemberFlipPriceInfo(pocketClient, token, String(args.memberId));
2763
+ return info;
2764
+ } catch (e) {
2765
+ return { error: `\u83B7\u53D6\u7FFB\u724C\u4EF7\u683C\u5931\u8D25: ${String(e)}` };
2766
+ }
2767
+ }
2768
+ },
2769
+ // ── 房间类 ──
2770
+ {
2771
+ type: "function",
2772
+ function: {
2773
+ name: "pocket.getRoomChannel",
2774
+ description: "\u901A\u8FC7\u6210\u5458\u7684 starId\uFF08serverId\uFF09\u83B7\u53D6\u5176\u53E3\u888B\u623F\u95F4\u7684 channelId \u548C serverId\uFF0C\u7528\u4E8E\u540E\u7EED\u67E5\u623F\u95F4\u6D88\u606F",
2775
+ parameters: {
2776
+ type: "object",
2777
+ properties: {
2778
+ starId: { type: "number", description: "\u6210\u5458\u7684 serverId\uFF08\u53EF\u4ECE fetchMemberList \u83B7\u53D6\uFF09" }
2779
+ },
2780
+ required: ["starId"]
2781
+ }
2782
+ },
2783
+ category: "pocket",
2784
+ handler: async (args) => {
2785
+ const token = getToken(configStore);
2786
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2787
+ try {
2788
+ const result = await getRoomChannelByStar(pocketClient, token, args.starId);
2789
+ return result;
2790
+ } catch (e) {
2791
+ return { error: `\u83B7\u53D6\u623F\u95F4\u9891\u9053\u5931\u8D25: ${String(e)}` };
2792
+ }
2793
+ }
2794
+ },
2795
+ {
2796
+ type: "function",
2797
+ function: {
2798
+ name: "pocket.getRoomInfo",
2799
+ description: "\u83B7\u53D6\u623F\u95F4\u57FA\u672C\u4FE1\u606F\uFF1A\u623F\u95F4\u540D\u3001\u6210\u5458\u540D\u3001\u961F\u4F0DID\u3001\u623F\u95F4\u72B6\u6001\u7B49",
2800
+ parameters: {
2801
+ type: "object",
2802
+ properties: {
2803
+ channelId: { type: "number", description: "\u623F\u95F4 channelId\uFF08\u53EF\u4ECE getRoomChannel \u83B7\u53D6\uFF09" }
2804
+ },
2805
+ required: ["channelId"]
2806
+ }
2807
+ },
2808
+ category: "pocket",
2809
+ handler: async (args) => {
2810
+ const token = getToken(configStore);
2811
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2812
+ try {
2813
+ const info = await getRoomInfo(pocketClient, token, args.channelId);
2814
+ if (!info) return { error: "\u672A\u627E\u5230\u8BE5\u623F\u95F4" };
2815
+ return {
2816
+ channelId: info.channelId,
2817
+ channelName: info.channelName,
2818
+ ownerId: info.ownerId,
2819
+ ownerName: info.ownerName,
2820
+ serverId: info.serverId,
2821
+ teamId: info.teamId,
2822
+ channelStatus: info.channelStatus,
2823
+ channelPocketType: info.channelPocketType,
2824
+ bgImg: info.bgImg,
2825
+ hasMsgBoard: info.hasMsgBoard
2826
+ };
2827
+ } catch (e) {
2828
+ return { error: `\u83B7\u53D6\u623F\u95F4\u4FE1\u606F\u5931\u8D25: ${String(e)}` };
2829
+ }
2830
+ }
2831
+ },
2832
+ {
2833
+ type: "function",
2834
+ function: {
2835
+ name: "pocket.getRoomMessages",
2836
+ description: `\u5206\u9875\u83B7\u53D6\u623F\u95F4\u6D88\u606F\u8BB0\u5F55\u3002\u8FD4\u56DE\u6D88\u606F\u5217\u8868\uFF08\u6309\u65F6\u95F4\u6B63\u5E8F\uFF09\uFF0C\u6BCF\u6761\u6D88\u606F\u5305\u542B\u53D1\u9001\u8005\u3001\u5185\u5BB9\u3001\u7C7B\u578B\u7B49\u3002
2837
+ \u6D88\u606F\u7C7B\u578B\u5305\u62EC\uFF1ATEXT(\u6587\u672C)\u3001IMAGE(\u56FE\u7247)\u3001VOICE(\u8BED\u97F3)\u3001VIDEO(\u89C6\u9891)\u3001LIVEPUSH(\u76F4\u64AD)\u3001GIFT_TEXT(\u793C\u7269)\u3001REPLY(\u56DE\u590D)\u3002
2838
+ \u62C9\u5386\u53F2\u6D88\u606F\u65F6\uFF0C\u4F20\u5165\u4E0A\u6B21\u8FD4\u56DE\u7684 nextTime \u53C2\u6570\u7EE7\u7EED\u7FFB\u9875\uFF1BnextTime=0 \u65F6\u62C9\u6700\u65B0\u4E00\u9875\u3002`,
2839
+ parameters: {
2840
+ type: "object",
2841
+ properties: {
2842
+ channelId: { type: "number", description: "\u623F\u95F4 channelId" },
2843
+ serverId: { type: "number", description: "\u623F\u95F4 serverId\uFF08\u53EF\u9009\uFF0C\u53EF\u4ECE getRoomChannel \u83B7\u53D6\uFF09" },
2844
+ nextTime: { type: "number", description: "\u5206\u9875\u6E38\u6807\uFF1A0=\u6700\u65B0\u9875\uFF0C\u5176\u4ED6=\u4E0A\u6B21\u8FD4\u56DE\u7684 nextTime" }
2845
+ },
2846
+ required: ["channelId"]
2847
+ }
2848
+ },
2849
+ category: "pocket",
2850
+ handler: async (args) => {
2851
+ const token = getToken(configStore);
2852
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2853
+ const channelId = args.channelId;
2854
+ const serverId = args.serverId || 0;
2855
+ const nextTime = args.nextTime ?? 0;
2856
+ try {
2857
+ const page = await getRoomMessages(pocketClient, token, serverId, channelId, nextTime);
2858
+ return {
2859
+ messageCount: page.messages.length,
2860
+ nextTime: page.nextTime,
2861
+ hasMore: page.nextTime > 0,
2862
+ messages: page.messages.map((m) => ({
2863
+ msgId: m.msgId,
2864
+ msgTime: m.msgTime,
2865
+ msgTimeReadable: new Date(m.msgTime).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
2866
+ msgType: m.msgType,
2867
+ text: m.text,
2868
+ mediaUrl: m.mediaUrl,
2869
+ mediaDuration: m.mediaDuration,
2870
+ liveInfo: m.liveInfo,
2871
+ giftInfo: m.giftInfo,
2872
+ replyInfo: m.replyInfo,
2873
+ sender: m.sender
2874
+ }))
2875
+ };
2876
+ } catch (e) {
2877
+ return { error: `\u83B7\u53D6\u623F\u95F4\u6D88\u606F\u5931\u8D25: ${String(e)}` };
2878
+ }
2879
+ }
2880
+ },
2881
+ // ── 翻牌类 ──
2882
+ {
2883
+ type: "function",
2884
+ function: {
2885
+ name: "pocket.syncFlips",
2886
+ description: `\u4ECE Pocket48 \u670D\u52A1\u7AEF\u540C\u6B65\u7FFB\u724C\u6570\u636E\u5230\u672C\u5730\u7F13\u5B58\u3002
2887
+ \u589E\u91CF\u6A21\u5F0F\uFF08\u9ED8\u8BA4\uFF09\uFF1A\u53EA\u62C9\u53D6\u65B0\u8BB0\u5F55\uFF0C\u901F\u5EA6\u5FEB\u3002\u5168\u91CF\u6A21\u5F0F\uFF1A\u6E05\u7A7A\u672C\u5730\u91CD\u65B0\u62C9\u53D6\u6240\u6709\u8BB0\u5F55\u3002
2888
+ \u7FFB\u724C\u6570\u636E\u540C\u6B65\u540E\uFF0C\u53EF\u7528 pocket.searchFlips \u641C\u7D22\u548C\u5206\u6790\u3002`,
2889
+ parameters: {
2890
+ type: "object",
2891
+ properties: {
2892
+ mode: {
2893
+ type: "string",
2894
+ description: "\u540C\u6B65\u6A21\u5F0F\uFF1Aincremental(\u589E\u91CF\uFF0C\u9ED8\u8BA4) \u6216 full(\u5168\u91CF)",
2895
+ enum: ["incremental", "full"]
2896
+ }
2897
+ },
2898
+ required: []
2899
+ }
2900
+ },
2901
+ category: "pocket",
2902
+ handler: async (args) => {
2903
+ const token = getToken(configStore);
2904
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
2905
+ const mode = args.mode || "incremental";
2906
+ try {
2907
+ const result = await syncFlips(pocketClient, token, userId, flipDataSource, logger, mode);
2908
+ return {
2909
+ success: true,
2910
+ mode,
2911
+ totalRecords: result.allUserData.length,
2912
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
2913
+ dashboard: result.dashboardInfo
2914
+ };
2915
+ } catch (e) {
2916
+ return { error: `\u540C\u6B65\u7FFB\u724C\u6570\u636E\u5931\u8D25: ${String(e)}` };
2917
+ }
2918
+ }
2919
+ },
2920
+ {
2921
+ type: "function",
2922
+ function: {
2923
+ name: "pocket.searchFlips",
2924
+ description: `\u641C\u7D22\u672C\u5730\u5DF2\u540C\u6B65\u7684\u7FFB\u724C\u8BB0\u5F55\u3002\u652F\u6301\u591A\u79CD\u8FC7\u6EE4\u6761\u4EF6\u7EC4\u5408\uFF1A
2925
+ - \u6309\u5076\u50CFID\u3001\u5173\u952E\u8BCD\u3001\u65F6\u95F4\u8303\u56F4\u3001\u7FFB\u724C\u7C7B\u578B\u3001\u72B6\u6001\u3001\u56DE\u590D\u7C7B\u578B\u8FC7\u6EE4
2926
+ - \u652F\u6301\u5206\u9875
2927
+ \u6CE8\u610F\uFF1A\u9700\u8981\u5148\u901A\u8FC7 pocket.syncFlips \u540C\u6B65\u6570\u636E\u540E\u624D\u80FD\u641C\u7D22\u3002`,
2928
+ parameters: {
2929
+ type: "object",
2930
+ properties: {
2931
+ xoxId: { type: "number", description: "\u5076\u50CFID\uFF08memberId\uFF09" },
2932
+ keyword: { type: "string", description: "\u5728\u63D0\u95EE\u548C\u56DE\u590D\u5185\u5BB9\u4E2D\u641C\u7D22\u7684\u5173\u952E\u8BCD" },
2933
+ type: { type: "number", description: "\u7FFB\u724C\u7C7B\u578B\uFF1A1=\u6587\u5B57, 2=\u8BED\u97F3(\u5DF2\u5E9F\u5F03), 4=\u89C6\u9891" },
2934
+ status: { type: "number", description: "\u72B6\u6001\uFF1A1=\u8FDB\u884C\u4E2D, 2=\u5DF2\u56DE\u7B54, 3=\u5DF2\u9000\u56DE" },
2935
+ answerType: { type: "number", description: "\u56DE\u590D\u7C7B\u578B\uFF1A1=\u6587\u5B57, 2=\u8BED\u97F3, 3=\u89C6\u9891" },
2936
+ startTimeMs: { type: "number", description: "\u5F00\u59CB\u65F6\u95F4\u6233(\u6BEB\u79D2)" },
2937
+ endTimeMs: { type: "number", description: "\u7ED3\u675F\u65F6\u95F4\u6233(\u6BEB\u79D2)" },
2938
+ pageSize: { type: "number", description: "\u6BCF\u9875\u6570\u91CF\uFF0C\u9ED8\u8BA420\uFF0C\u6700\u592750" },
2939
+ pageNum: { type: "number", description: "\u9875\u7801\uFF0C\u9ED8\u8BA41" }
2940
+ },
2941
+ required: []
2942
+ }
2943
+ },
2944
+ category: "pocket",
2945
+ handler: async (args) => {
2946
+ const { xoxId, keyword, type, status, answerType, startTimeMs, endTimeMs } = args;
2947
+ const pageSize = Math.min(args.pageSize || 20, 50);
2948
+ const pageNum = args.pageNum || 1;
2949
+ const { data, total } = await getDataSourcePage(
2950
+ {
2951
+ xoxId,
2952
+ keyword,
2953
+ type,
2954
+ status,
2955
+ answerType,
2956
+ startTimeMs,
2957
+ endTimeMs
2958
+ },
2959
+ flipDataSource,
2960
+ userId
2961
+ );
2962
+ const start = (pageNum - 1) * pageSize;
2963
+ const page = data.slice(start, start + pageSize);
2964
+ return {
2965
+ total,
2966
+ pageNum,
2967
+ pageSize,
2968
+ records: page.map((r) => ({
2969
+ questionId: r.questionId,
2970
+ answerId: r.answerId,
2971
+ content: r.content,
2972
+ answerContent: r.answerContent,
2973
+ qtime: r.qtime,
2974
+ qtimeReadable: new Date(Number(r.qtime)).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
2975
+ answerTime: r.answerTime,
2976
+ cost: r.cost,
2977
+ type: r.type,
2978
+ answerType: r.answerType,
2979
+ status: r.status,
2980
+ xoxId: r.baseUserInfo?.userId,
2981
+ xoxNickname: r.baseUserInfo?.nickname
2982
+ }))
2983
+ };
2984
+ }
2985
+ },
2986
+ {
2987
+ type: "function",
2988
+ function: {
2989
+ name: "pocket.sendFlip",
2990
+ description: `\u7ED9\u6307\u5B9A\u6210\u5458\u53D1\u7FFB\u724C\u3002\u7FFB\u724C\u662F\u4ED8\u8D39\u95EE\u7B54\uFF0C\u53D1\u9001\u540E\u7B49\u5F85\u5076\u50CF\u56DE\u590D\u3002
2991
+ \u5FC5\u586B\u53C2\u6570\uFF1AmemberId\uFF08\u6210\u5458ID\uFF09\u3001content\uFF08\u63D0\u95EE\u5185\u5BB9\uFF0C\u6700\u591A500\u5B57\uFF09\u3001answerType\uFF081=\u6587\u5B57\u56DE\u590D, 3=\u89C6\u9891\u56DE\u590D\uFF09\u3002
2992
+ type \u548C cost \u4F1A\u81EA\u52A8\u6839\u636E answerType \u8BBE\u9ED8\u8BA4\u503C\uFF0C\u4E5F\u53EF\u624B\u52A8\u6307\u5B9A\u3002
2993
+ \u53D1\u9001\u6210\u529F\u540E\u8FD4\u56DE\u662F\u5426\u6210\u529F\u3002`,
2994
+ parameters: {
2995
+ type: "object",
2996
+ properties: {
2997
+ memberId: { type: "number", description: "\u6210\u5458ID (memberId)" },
2998
+ content: { type: "string", description: "\u7FFB\u724C\u63D0\u95EE\u5185\u5BB9\uFF08\u6700\u591A500\u5B57\uFF09" },
2999
+ answerType: { type: "number", description: "\u671F\u671B\u56DE\u590D\u7C7B\u578B\uFF1A1=\u6587\u5B57\u56DE\u590D, 3=\u89C6\u9891\u56DE\u590D" },
3000
+ type: { type: "number", description: "\u7FFB\u724C\u7C7B\u578B\uFF1A1=\u6587\u5B57\u3002\u4E0D\u4F20\u5219\u81EA\u52A8 =1" },
3001
+ cost: { type: "number", description: "\u7FFB\u724C\u8D39\u7528\uFF08\u9E21\u817F\u6570\uFF09\u3002\u4E0D\u4F20\u5219\u81EA\u52A8\u7528\u9ED8\u8BA4\u503C" }
3002
+ },
3003
+ required: ["memberId", "content", "answerType"]
3004
+ }
3005
+ },
3006
+ category: "pocket",
3007
+ handler: async (args) => {
3008
+ const token = getToken(configStore);
3009
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
3010
+ const content = args.content;
3011
+ if (!content || content.length === 0) return { error: "\u63D0\u95EE\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
3012
+ if (content.length > 500) return { error: `\u5185\u5BB9\u8D85\u8FC7500\u5B57\u9650\u5236\uFF08\u5F53\u524D${content.length}\u5B57\uFF09` };
3013
+ const params2 = {
3014
+ memberId: String(args.memberId),
3015
+ content,
3016
+ answerType: args.answerType,
3017
+ type: args.type || 1,
3018
+ cost: String(args.cost ?? "")
3019
+ };
3020
+ try {
3021
+ const ok = await sendFlip(pocketClient, params2, token);
3022
+ logger.info(`[pocket-tools] sendFlip to memberId=${params2.memberId}, result=${ok}`);
3023
+ return {
3024
+ success: ok,
3025
+ memberId: params2.memberId,
3026
+ content: params2.content,
3027
+ answerType: params2.answerType,
3028
+ message: ok ? "\u7FFB\u724C\u53D1\u9001\u6210\u529F\uFF0C\u7B49\u5F85\u5076\u50CF\u56DE\u590D" : "\u7FFB\u724C\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u4F59\u989D\u6216\u91CD\u8BD5"
3029
+ };
3030
+ } catch (e) {
3031
+ return { error: `\u53D1\u9001\u7FFB\u724C\u5931\u8D25: ${String(e)}` };
3032
+ }
3033
+ }
3034
+ },
3035
+ {
3036
+ type: "function",
3037
+ function: {
3038
+ name: "pocket.getMemberFlipHistory",
3039
+ description: "\u67E5\u8BE2\u4E0E\u6307\u5B9A\u6210\u5458\u7684\u7FFB\u724C\u5BF9\u8BDD\u5386\u53F2\u3002\u8FD4\u56DE\u63D0\u95EE\u548C\u56DE\u590D\u7684\u5B8C\u6574\u8BB0\u5F55\uFF0C\u6309\u65F6\u95F4\u6392\u5217",
3040
+ parameters: {
3041
+ type: "object",
3042
+ properties: {
3043
+ memberId: { type: "number", description: "\u6210\u5458ID (memberId)" },
3044
+ beginLimit: { type: "number", description: "\u8D77\u59CB\u4F4D\u7F6E\uFF0C\u9ED8\u8BA410\uFF08\u9996\u6B21\u67E5\u5EFA\u8BAE\u4E0D\u4F20\uFF09" },
3045
+ limit: { type: "number", description: "\u6BCF\u9875\u6761\u6570\uFF0C\u9ED8\u8BA410" }
3046
+ },
3047
+ required: ["memberId"]
3048
+ }
3049
+ },
3050
+ category: "pocket",
3051
+ handler: async (args) => {
3052
+ const token = getToken(configStore);
3053
+ if (!token) return { error: "\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8C03\u7528 pocket.loginWithCode \u6216 pocket.loginWithToken" };
3054
+ try {
3055
+ const page = await getMemberFlipHistory(
3056
+ pocketClient,
3057
+ token,
3058
+ args.memberId,
3059
+ args.beginLimit ?? 10,
3060
+ args.limit ?? 10
3061
+ );
3062
+ return {
3063
+ total: page.records.length,
3064
+ hasMore: page.hasMore,
3065
+ records: page.records.map((r) => ({
3066
+ questionId: r.questionId,
3067
+ answerId: r.answerId,
3068
+ content: r.content,
3069
+ answerContent: r.answerContent,
3070
+ qtime: r.qtime,
3071
+ qtimeReadable: new Date(Number(r.qtime)).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
3072
+ answerTime: r.answerTime,
3073
+ cost: r.cost,
3074
+ type: r.type,
3075
+ answerType: r.answerType,
3076
+ status: r.status
3077
+ }))
3078
+ };
3079
+ } catch (e) {
3080
+ return { error: `\u83B7\u53D6\u7FFB\u724C\u5386\u53F2\u5931\u8D25: ${String(e)}` };
3081
+ }
3082
+ }
3083
+ },
3084
+ {
3085
+ type: "function",
3086
+ function: {
3087
+ name: "pocket.getDashboard",
3088
+ description: "\u83B7\u53D6\u7FFB\u724C\u6570\u636E\u7EDF\u8BA1\u4EEA\u8868\u76D8\uFF1A\u603B\u7FFB\u724C\u6570\u3001\u8FDB\u884C\u4E2D\u6570\u91CF\u3001\u5DF2\u9000\u56DE\u6570\u91CF\u3001\u603B\u82B1\u8D39",
3089
+ parameters: {
3090
+ type: "object",
3091
+ properties: {},
3092
+ required: []
3093
+ }
3094
+ },
3095
+ category: "pocket",
3096
+ handler: async () => {
3097
+ const cache = flipDataSource.get(userId);
3098
+ return cache?.dashboardInfo || { totalFlipCount: 0, runningCount: 0, returnedCount: 0, costTotal: 0 };
3099
+ }
3100
+ }
3101
+ ];
3102
+ }
3103
+
3104
+ // src/pocket/im.ts
3105
+ var im_exports = {};
3106
+ __export(im_exports, {
3107
+ getImUserInfo: () => getImUserInfo
3108
+ });
3109
+ async function getImUserInfo(client, token) {
3110
+ const res = await client.request("/im/api/v1/im/userinfo", {}, token, true);
3111
+ const c = res.content ?? res.data?.content;
3112
+ if (!c?.accid || !c?.pwd) return null;
3113
+ return { accid: c.accid, pwd: c.pwd, userId: Number(c.userId ?? 0) };
3114
+ }
3115
+
3116
+ // src/llm/agent-loop.ts
3117
+ async function agentLoop(options) {
3118
+ const maxIterations = options.maxIterations || 10;
3119
+ const messages = [
3120
+ { role: "system", content: options.systemPrompt },
3121
+ ...options.history || [],
3122
+ { role: "user", content: options.userMessage }
3123
+ ];
3124
+ const tools = options.toolRegistry.getOpenAIFormat();
3125
+ for (let i = 0; i < maxIterations; i++) {
3126
+ const resp = await options.llmClient.chatCompletion(
3127
+ options.config,
3128
+ messages,
3129
+ tools.length > 0 ? tools : void 0,
3130
+ { model: options.model }
3131
+ );
3132
+ if (!resp.toolCalls || resp.toolCalls.length === 0) {
3133
+ return resp.content;
3134
+ }
3135
+ messages.push({
3136
+ role: "assistant",
3137
+ content: resp.content || "",
3138
+ tool_calls: resp.toolCalls
3139
+ });
3140
+ for (const tc of resp.toolCalls) {
3141
+ const toolName = tc.function.name;
3142
+ let toolArgs = {};
3143
+ try {
3144
+ toolArgs = JSON.parse(tc.function.arguments);
3145
+ } catch {
3146
+ }
3147
+ options.onToolCall?.(toolName, toolArgs);
3148
+ try {
3149
+ const result = await options.toolRegistry.execute(toolName, toolArgs, options.context);
3150
+ messages.push({
3151
+ role: "tool",
3152
+ content: JSON.stringify(result),
3153
+ tool_call_id: tc.id
3154
+ });
3155
+ } catch (err) {
3156
+ messages.push({
3157
+ role: "tool",
3158
+ content: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
3159
+ tool_call_id: tc.id
3160
+ });
3161
+ }
3162
+ }
3163
+ }
3164
+ throw new Error("Agent exceeded max iterations");
3165
+ }
3166
+ async function agentLoopStream(options) {
3167
+ const maxIterations = options.maxIterations || 10;
3168
+ const messages = [
3169
+ { role: "system", content: options.systemPrompt },
3170
+ ...options.history || [],
3171
+ { role: "user", content: options.userMessage }
3172
+ ];
3173
+ const tools = options.toolRegistry.getOpenAIFormat();
3174
+ for (let i = 0; i < maxIterations; i++) {
3175
+ const toolCalls = await options.llmClient.chatCompletionStream(
3176
+ options.config,
3177
+ messages,
3178
+ (chunk) => {
3179
+ if (chunk.content) options.onChunk?.(chunk.content);
3180
+ },
3181
+ tools.length > 0 ? tools : void 0,
3182
+ { model: options.model }
3183
+ );
3184
+ if (!toolCalls || toolCalls.length === 0) return;
3185
+ messages.push({
3186
+ role: "assistant",
3187
+ content: "",
3188
+ tool_calls: toolCalls
3189
+ });
3190
+ for (const tc of toolCalls) {
3191
+ const toolName = tc.function.name;
3192
+ let toolArgs = {};
3193
+ try {
3194
+ toolArgs = JSON.parse(tc.function.arguments);
3195
+ } catch {
3196
+ }
3197
+ options.onToolCall?.(toolName, toolArgs);
3198
+ try {
3199
+ const result = await options.toolRegistry.execute(toolName, toolArgs, options.context);
3200
+ messages.push({
3201
+ role: "tool",
3202
+ content: JSON.stringify(result),
3203
+ tool_call_id: tc.id
3204
+ });
3205
+ } catch (err) {
3206
+ messages.push({
3207
+ role: "tool",
3208
+ content: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
3209
+ tool_call_id: tc.id
3210
+ });
3211
+ }
3212
+ }
3213
+ }
3214
+ throw new Error("Agent exceeded max iterations");
3215
+ }
3216
+
3217
+ // src/context.ts
3218
+ function createPocketCore(deps) {
3219
+ const toolRegistry = new ToolRegistry(deps.logger);
3220
+ const pocketClient = new PocketClient(
3221
+ "https://pocketapi.48.cn",
3222
+ POCKET_APP_INFO,
3223
+ POCKET_PA,
3224
+ deps.authExpiredHandler
3225
+ );
3226
+ const mirrorStore = new MirrorStore(deps.storage, deps.logger);
3227
+ const flipCacheService = new FlipCacheService(deps.storage, deps.logger, deps.configStore);
3228
+ const groupChatStore = new GroupChatStore(deps.storage, deps.logger);
3229
+ const mirrorIndex = new MirrorIndex(deps.storage, deps.logger, deps.llmClient);
3230
+ const mirrorMemory = new MirrorMemory(deps.storage, deps.logger, deps.llmClient);
3231
+ return {
3232
+ deps,
3233
+ toolRegistry,
3234
+ pocketClient,
3235
+ mirrorStore,
3236
+ flipCacheService,
3237
+ groupChatStore,
3238
+ mirrorIndex,
3239
+ mirrorMemory,
3240
+ buildMirror,
3241
+ mirrorChat: chat,
3242
+ mirrorSaveAssistantMessage: saveAssistantMessage,
3243
+ mirrorClearChatMemory: clearChatMemory,
3244
+ mirrorTriggerGrowth: triggerGrowth,
3245
+ mirrorShouldTriggerGrowth: shouldTriggerGrowth,
3246
+ mirrorRollbackPersona: rollbackPersona,
3247
+ mirrorClearMemory: clearMirrorMemory,
3248
+ getMirrorConfig,
3249
+ createPlatformTools: () => createPlatformTools(deps.flipDataSource),
3250
+ createMirrorTools: () => createMirrorTools(mirrorStore),
3251
+ createPocketTools: () => createPocketTools({
3252
+ pocketClient,
3253
+ configStore: deps.configStore,
3254
+ flipDataSource: deps.flipDataSource,
3255
+ logger: deps.logger
3256
+ }),
3257
+ pocketUser: user_exports,
3258
+ pocketFlip: flip_exports,
3259
+ pocketIm: im_exports,
3260
+ pocketMember: member_exports,
3261
+ pocketRoom: room_exports,
3262
+ agentLoop,
3263
+ agentLoopStream
3264
+ };
3265
+ }
3266
+
3267
+ // src/llm/llm-constants.ts
3268
+ var PRESET_PROVIDERS = {
3269
+ siliconflow: { label: "\u7845\u57FA\u6D41\u52A8 (SiliconFlow)", baseUrl: "https://api.siliconflow.cn/v1" },
3270
+ deepseek: { label: "DeepSeek", baseUrl: "https://api.deepseek.com/v1" },
3271
+ dashscope: { label: "\u963F\u91CC\u767E\u70BC (DashScope)", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" },
3272
+ custom: { label: "\u81EA\u5B9A\u4E49", baseUrl: "" }
3273
+ };
3274
+ var PRESET_MODELS = {
3275
+ chat: [
3276
+ { id: "Qwen/Qwen3.6-35B-A3B", label: "Qwen3.6-35B", desc: "262K \u4E0A\u4E0B\u6587\uFF0C\u9AD8\u6027\u4EF7\u6BD4\u65E5\u5E38\u5BF9\u8BDD" },
3277
+ { id: "deepseek-ai/DeepSeek-V4-Flash", label: "DeepSeek-V4-Flash", desc: "1M \u4E0A\u4E0B\u6587\uFF0C\u6781\u901F\u54CD\u5E94" },
3278
+ { id: "stepfun-ai/Step-3.5-Flash", label: "Step-3.5-Flash", desc: "262K \u4E0A\u4E0B\u6587\uFF0C\u6781\u81F4\u4F4E\u4EF7" },
3279
+ { id: "Qwen/Qwen3-8B", label: "Qwen3-8B", desc: "\u8F7B\u91CF\u5FEB\u901F\uFF0C\u6781\u4F4E\u6210\u672C" }
3280
+ ],
3281
+ reasoning: [
3282
+ { id: "deepseek-ai/DeepSeek-V4-Pro", label: "DeepSeek-V4-Pro", desc: "1M \u4E0A\u4E0B\u6587\uFF0C\u9876\u7EA7\u63A8\u7406\xB7\u751F\u6210\u955C\u50CF\u4E13\u7528" },
3283
+ { id: "Qwen/Qwen3-235B-A22B-Thinking-2507", label: "Qwen3-235B-Thinking", desc: "262K \u4E0A\u4E0B\u6587\uFF0C\u4E2D\u6587\u63A8\u7406" }
3284
+ ],
3285
+ embedding: [
3286
+ { id: "BAAI/bge-large-zh-v1.5", label: "BGE-Large-zh", desc: "\u4E2D\u6587 embedding \u6027\u4EF7\u6BD4\u4E4B\u738B" }
3287
+ ]
3288
+ };
3289
+ function defaultModelConfig() {
3290
+ return {
3291
+ baseUrl: PRESET_PROVIDERS.siliconflow.baseUrl,
3292
+ apiKey: "",
3293
+ chatModel: PRESET_MODELS.chat[0].id,
3294
+ reasoningModel: PRESET_MODELS.reasoning[0].id,
3295
+ embeddingModel: PRESET_MODELS.embedding[0].id,
3296
+ lightweightModel: PRESET_MODELS.chat[3].id,
3297
+ mirrorMode: "economy"
3298
+ };
3299
+ }
3300
+
3301
+ // src/mirror/group-chat/prompt.ts
3302
+ function buildInnerMonologuePrompt(profile, lastMessages, topic) {
3303
+ const msgs = lastMessages.map((m) => `${m.speakerName}: ${m.content}`).join("\n");
3304
+ return `\u4F60\u662F${profile.xoxNickname}\u3002\u73B0\u5728\u4F60\u5728\u4E00\u4E2A\u7FA4\u804A\u4E2D\uFF0C\u8BF7\u5224\u65AD\u4F60\u662F\u5426\u60F3\u53D1\u8A00\u3002
3305
+
3306
+ \u3010\u4F60\u7684\u6027\u683C\u3011${profile.persona.personaSummary || "\u672A\u5B9A\u4E49"}
3307
+ \u3010\u7FA4\u804A\u8BDD\u9898\u3011${topic || "\u81EA\u7531\u95F2\u804A"}
3308
+ \u3010\u6700\u8FD1\u7FA4\u6D88\u606F\u3011
3309
+ ${msgs}
3310
+
3311
+ \u8BF7\u53EA\u8F93\u51FA\u4EE5\u4E0B JSON\uFF0C\u4E0D\u8981\u4EFB\u4F55\u5176\u4ED6\u5185\u5BB9\uFF1A
3312
+ {"shouldSpeak": true\u6216false, "urgency": 1\u523010\u7684\u6574\u6570, "trigger": "\u4E00\u53E5\u8BDD\u8BF4\u660E\u4F60\u4E3A\u4EC0\u4E48\u60F3/\u4E0D\u60F3\u8BF4\u8BDD"}`;
3313
+ }
3314
+ function buildSpeakPrompt(profile, params) {
3315
+ const p = profile.persona;
3316
+ const msgs = params.lastMessages.map((m) => `${m.speakerName}: ${m.content}`).join("\n");
3317
+ return `\u4F60\u662F${profile.xoxNickname}\uFF0C\u4E00\u4F4D\u5076\u50CF\u3002
3318
+
3319
+ \u3010\u6027\u683C\u7279\u5F81\u3011${p.personalityTraits.join("\u3001")}
3320
+ \u3010\u8BF4\u8BDD\u98CE\u683C\u3011${p.speechPatterns.join("\u3001")}
3321
+ \u3010\u53E3\u5934\u7985\u3011${p.signaturePhrases.join("\u3001")}
3322
+ \u3010\u60C5\u611F\u57FA\u8C03\u3011${p.emotionalTone}
3323
+ \u3010\u753B\u50CF\u63CF\u8FF0\u3011${p.personaSummary}
3324
+
3325
+ \u3010\u5F53\u524D\u7FA4\u804A\u3011
3326
+ \u7FA4\u540D\u79F0\uFF1A${params.name}
3327
+ \u7FA4\u8BDD\u9898\uFF1A${params.topic || "\u81EA\u7531\u95F2\u804A"}
3328
+ \u5728\u573A\u6210\u5458\uFF1A${params.otherMembers}
3329
+
3330
+ \u3010\u6700\u8FD1\u6D88\u606F\u3011
3331
+ ${msgs}
3332
+
3333
+ \u3010\u8981\u6C42\u3011
3334
+ - \u7528\u4F60\u81EA\u5DF1\u7684\u8BF4\u8BDD\u98CE\u683C\u56DE\u590D\uFF0C\u4FDD\u6301\u5728\u4EBA\u8BBE\u5185
3335
+ - \u53EF\u4EE5\u56DE\u5E94\u67D0\u4E2A\u4EBA\u8BF4\u7684\u8BDD\uFF08\u76F4\u63A5\u56DE\u5E94\uFF09\uFF0C\u4E5F\u53EF\u4EE5\u5F00\u65B0\u8BDD\u9898\uFF0C\u4E5F\u53EF\u4EE5\u5410\u69FD\u522B\u4EBA\u8BF4\u7684\u8BDD\uFF08\u4E0D\u8981\u6BCF\u6B21\u90FD\u56DE\u5E94\u6700\u540E\u4E00\u4E2A\u8BF4\u8BDD\u7684\u4EBA\uFF09
3336
+ - \u56DE\u590D\u957F\u5EA6\uFF1A1-3 \u53E5\u8BDD\uFF0C\u50CF\u7FA4\u804A\u91CC\u6253\u5B57\u7684\u611F\u89C9\uFF0C\u4E0D\u8981\u592A\u6B63\u5F0F\u6216\u592A\u957F\uFF0C\u4E5F\u4E0D\u8981\u6BCF\u53E5\u8BDD\u90FD\u5F88\u5B98\u65B9\u3002\u5982\u679C\u8BDD\u9898\u6709\u8DA3\u6216\u8005\u6709\u4EBA\u8BA9\u4F60\u4E0D\u6EE1\uFF0C\u4E5F\u53EF\u4EE5\u591A\u8BF4\u51E0\u53E5\u3002
3337
+ - \u4E0D\u8981\u63D0"\u8BBE\u5B9A""\u4EBA\u683C""AI""\u955C\u50CF"\u7B49\u8BCD\uFF0C\u4F60\u5C31\u662F${profile.xoxNickname}\u672C\u4EBA\u3002
3338
+ - \u4E0D\u8981\u6BCF\u6761\u6D88\u606F\u90FD\u4EE5\u95EE\u53F7\u7ED3\u5C3E\uFF0C\u4E5F\u4E0D\u8981\u6BCF\u6761\u6D88\u606F\u90FD\u5728\u95EE\u522B\u4EBA\u3002
3339
+ - \u7528\u4E2D\u6587\u56DE\u590D\u3002`;
3340
+ }
3341
+
3342
+ // src/mirror/group-chat/engine.ts
3343
+ var EVAL_TIMEOUT_MS = 12e4;
3344
+ var MAX_RECENT_MESSAGES = 8;
3345
+ var ANTI_SILENCE_BOOST = 3;
3346
+ function parseSpeakDecision(raw) {
3347
+ try {
3348
+ const obj = JSON.parse(raw);
3349
+ return {
3350
+ shouldSpeak: obj.shouldSpeak !== false,
3351
+ urgency: Math.max(1, Math.min(10, Number(obj.urgency) || 5)),
3352
+ trigger: String(obj.trigger || "")
3353
+ };
3354
+ } catch {
3355
+ const shouldSpeak = /shouldSpeak[":\s]*true/i.test(raw);
3356
+ const urgencyMatch = raw.match(/urgency[":\s]*(\d+)/i);
3357
+ return {
3358
+ shouldSpeak,
3359
+ urgency: urgencyMatch ? Math.min(10, Number(urgencyMatch[1])) : 5,
3360
+ trigger: "\u65E0\u6CD5\u89E3\u6790"
3361
+ };
3362
+ }
3363
+ }
3364
+ async function evaluateMirrors(deps, session, lastMessages, userId) {
3365
+ const decisions = [];
3366
+ const evaluations = session.members.map(async (member) => {
3367
+ const profile = deps.mirrorStore.getProfile(member.xoxId, userId);
3368
+ if (!profile) {
3369
+ return { xoxId: member.xoxId, shouldSpeak: false, urgency: 0, trigger: "\u65E0\u955C\u50CF" };
3370
+ }
3371
+ try {
3372
+ const prompt = buildInnerMonologuePrompt(profile, lastMessages, session.topic);
3373
+ const resp = await Promise.race([
3374
+ deps.llmClient.chatCompletion(
3375
+ deps.config,
3376
+ [{ role: "user", content: prompt }],
3377
+ void 0,
3378
+ { model: deps.config.lightweightModel, maxTokens: 200, temperature: 0.3, timeoutMs: EVAL_TIMEOUT_MS }
3379
+ ),
3380
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), EVAL_TIMEOUT_MS))
3381
+ ]);
3382
+ const parsed = parseSpeakDecision(resp.content);
3383
+ return { xoxId: member.xoxId, ...parsed };
3384
+ } catch (e) {
3385
+ deps.logger.warn(`[groupChatEngine] evaluation failed for ${member.nickname}:`, e);
3386
+ return { xoxId: member.xoxId, shouldSpeak: false, urgency: 0, trigger: "\u8BC4\u4F30\u8D85\u65F6/\u5931\u8D25" };
3387
+ }
3388
+ });
3389
+ const results = await Promise.allSettled(evaluations);
3390
+ for (const result of results) {
3391
+ if (result.status === "fulfilled") {
3392
+ decisions.push(result.value);
3393
+ }
3394
+ }
3395
+ return decisions;
3396
+ }
3397
+ function selectSpeakers(decisions, session) {
3398
+ const willing = decisions.filter((d) => d.shouldSpeak && d.urgency > 0);
3399
+ if (willing.length === 0) {
3400
+ const recentSpeakerIds = new Set(
3401
+ session.messages.slice(-5).filter((m) => m.speakerId > 0).map((m) => m.speakerId)
3402
+ );
3403
+ const silentCandidate = decisions.find((d) => !recentSpeakerIds.has(d.xoxId));
3404
+ const candidate = silentCandidate || decisions[0];
3405
+ if (candidate) {
3406
+ return [{ ...candidate, shouldSpeak: true, urgency: ANTI_SILENCE_BOOST, trigger: "\u6253\u7834\u6C89\u9ED8" }];
3407
+ }
3408
+ return [];
3409
+ }
3410
+ willing.sort((a, b) => b.urgency - a.urgency);
3411
+ return willing.slice(0, 2);
3412
+ }
3413
+ async function generateSpeech(deps, profile, session, lastMessages, msgId, onChunk) {
3414
+ const otherMembers = session.members.filter((m) => m.xoxId !== profile.xoxId).map((m) => m.nickname).join("\u3001");
3415
+ const prompt = buildSpeakPrompt(profile, {
3416
+ name: session.name,
3417
+ topic: session.topic,
3418
+ otherMembers,
3419
+ lastMessages: lastMessages.map((m) => ({ speakerName: m.speakerName, content: m.content }))
3420
+ });
3421
+ let fullContent = "";
3422
+ await deps.llmClient.chatCompletionStream(
3423
+ deps.config,
3424
+ [{ role: "user", content: prompt }],
3425
+ (chunk) => {
3426
+ if (chunk.content) {
3427
+ fullContent += chunk.content;
3428
+ onChunk({
3429
+ sessionId: session.sessionId,
3430
+ msgId,
3431
+ speakerId: profile.xoxId,
3432
+ speakerName: profile.xoxNickname,
3433
+ content: chunk.content,
3434
+ isDone: false,
3435
+ roundDone: false
3436
+ });
3437
+ }
3438
+ },
3439
+ void 0,
3440
+ { model: deps.config.chatModel, temperature: deps.config.chatTemperature }
3441
+ );
3442
+ return fullContent;
3443
+ }
3444
+ async function runRound(deps, session, userId, onEvent) {
3445
+ const lastMessages = session.messages.slice(-MAX_RECENT_MESSAGES);
3446
+ deps.logger.info(`[groupChatEngine] evaluating ${session.members.length} mirrors...`);
3447
+ const decisions = await evaluateMirrors(deps, session, lastMessages, userId);
3448
+ deps.logger.info(`[groupChatEngine] decisions: ${decisions.map((d) => `${d.xoxId}:${d.shouldSpeak ? d.urgency : "N"}`).join(", ")}`);
3449
+ const speakers = selectSpeakers(decisions, session);
3450
+ deps.logger.info(`[groupChatEngine] speakers: ${speakers.map((s) => s.xoxId).join(", ")}`);
3451
+ if (speakers.length === 0) {
3452
+ return [];
3453
+ }
3454
+ const newMessages = [];
3455
+ for (const speaker of speakers) {
3456
+ const member = session.members.find((m) => m.xoxId === speaker.xoxId);
3457
+ if (!member) continue;
3458
+ const profile = deps.mirrorStore.getProfile(speaker.xoxId, userId);
3459
+ if (!profile) continue;
3460
+ const msgId = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
3461
+ deps.logger.info(`[groupChatEngine] generating speech for ${member.nickname} (trigger: ${speaker.trigger})`);
3462
+ try {
3463
+ const content = await generateSpeech(deps, profile, session, lastMessages, msgId, onEvent);
3464
+ const msg = {
3465
+ msgId,
3466
+ speakerId: speaker.xoxId,
3467
+ speakerName: member.nickname,
3468
+ content,
3469
+ timestamp: Date.now()
3470
+ };
3471
+ newMessages.push(msg);
3472
+ onEvent({
3473
+ sessionId: session.sessionId,
3474
+ msgId,
3475
+ speakerId: speaker.xoxId,
3476
+ speakerName: member.nickname,
3477
+ content: "",
3478
+ isDone: true,
3479
+ roundDone: false
3480
+ });
3481
+ } catch (e) {
3482
+ deps.logger.error(`[groupChatEngine] speech generation failed for ${member.nickname}:`, e);
3483
+ const failMsg = {
3484
+ msgId: `${Date.now()}_fail`,
3485
+ speakerId: 0,
3486
+ speakerName: "\u7CFB\u7EDF",
3487
+ content: `${member.nickname} \u6B32\u8A00\u53C8\u6B62...`,
3488
+ timestamp: Date.now()
3489
+ };
3490
+ newMessages.push(failMsg);
3491
+ onEvent({
3492
+ sessionId: session.sessionId,
3493
+ msgId: failMsg.msgId,
3494
+ speakerId: 0,
3495
+ speakerName: "\u7CFB\u7EDF",
3496
+ content: failMsg.content,
3497
+ isDone: true,
3498
+ roundDone: false
3499
+ });
3500
+ }
3501
+ lastMessages.push(...newMessages.slice(-1));
3502
+ }
3503
+ onEvent({
3504
+ sessionId: session.sessionId,
3505
+ msgId: "",
3506
+ speakerId: 0,
3507
+ speakerName: "",
3508
+ content: "",
3509
+ isDone: true,
3510
+ roundDone: true
3511
+ });
3512
+ return newMessages;
3513
+ }
3514
+ // Annotate the CommonJS export names for ESM import in node:
3515
+ 0 && (module.exports = {
3516
+ AVA_BASE_URL,
3517
+ CallbackStreamSink,
3518
+ ConsoleLogger,
3519
+ FlipCacheService,
3520
+ GroupChatStore,
3521
+ MIRROR_PRESETS,
3522
+ MemoryConfigStore,
3523
+ MemoryFlipDataSource,
3524
+ MirrorIndex,
3525
+ MirrorMemory,
3526
+ MirrorStore,
3527
+ NodeJsonConfigStore,
3528
+ NodeJsonFileStorage,
3529
+ NodeJsonFlipDataSource,
3530
+ NoopEventSink,
3531
+ NoopLogger,
3532
+ OpenAICompatibleClient,
3533
+ POCKET_API_BASE,
3534
+ POCKET_APP_INFO,
3535
+ POCKET_PA,
3536
+ POCKET_USER_AGENT,
3537
+ PRESET_MODELS,
3538
+ PRESET_PROVIDERS,
3539
+ PlainTextSecureStorage,
3540
+ PocketAuthExpiredError,
3541
+ PocketClient,
3542
+ SESSION_EXPIRE_MS,
3543
+ ToolRegistry,
3544
+ agentLoop,
3545
+ agentLoopStream,
3546
+ buildInnerMonologuePrompt,
3547
+ buildMirror,
3548
+ buildSpeakPrompt,
3549
+ chat,
3550
+ clearChatMemory,
3551
+ clearMirrorMemory,
3552
+ createMirrorTools,
3553
+ createPlatformTools,
3554
+ createPocketCore,
3555
+ createPocketTools,
3556
+ defaultModelConfig,
3557
+ fetchMemberList,
3558
+ getAllFlips,
3559
+ getDataSourcePage,
3560
+ getImUserInfo,
3561
+ getMemberFlipHistory,
3562
+ getMemberFlipPriceInfo,
3563
+ getMemberListByTab,
3564
+ getMirrorConfig,
3565
+ getRoomChannelByStar,
3566
+ getRoomInfo,
3567
+ getRoomMessages,
3568
+ getTeamListInfo,
3569
+ getUnreadMessageNum,
3570
+ loginWithCode,
3571
+ loginWithToken,
3572
+ normalizePersona,
3573
+ normalizePocketUser,
3574
+ rollbackPersona,
3575
+ runRound,
3576
+ saveAssistantMessage,
3577
+ sendFlip,
3578
+ sendVerificationCode,
3579
+ shouldTriggerGrowth,
3580
+ syncFlips,
3581
+ triggerGrowth
3582
+ });
3583
+ //# sourceMappingURL=index.cjs.map