aicodeswitch 5.2.11 → 5.2.13

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.
@@ -19,6 +19,13 @@ var __rest = (this && this.__rest) || function (s, e) {
19
19
  }
20
20
  return t;
21
21
  };
22
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var m = o[Symbol.asyncIterator], i;
25
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
26
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
27
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
28
+ };
22
29
  var __importDefault = (this && this.__importDefault) || function (mod) {
23
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
24
31
  };
@@ -67,9 +74,6 @@ class FileSystemDatabaseManager {
67
74
  get rulesFile() { return path_1.default.join(this.dataPath, 'rules.json'); } // legacy
68
75
  get configFile() { return path_1.default.join(this.dataPath, 'config.json'); }
69
76
  get sessionsFile() { return path_1.default.join(this.dataPath, 'sessions.json'); }
70
- get logsDir() { return path_1.default.join(this.dataPath, 'logs'); }
71
- get logsIndexFile() { return path_1.default.join(this.dataPath, 'logs-index.json'); }
72
- get sessionLogIndexFile() { return path_1.default.join(this.dataPath, 'session-log-index.json'); }
73
77
  get errorLogsFile() { return path_1.default.join(this.dataPath, 'error-logs.json'); }
74
78
  get blacklistFile() { return path_1.default.join(this.dataPath, 'blacklist.json'); }
75
79
  get statisticsFile() { return path_1.default.join(this.dataPath, 'statistics.json'); }
@@ -112,6 +116,12 @@ class FileSystemDatabaseManager {
112
116
  writable: true,
113
117
  value: void 0
114
118
  });
119
+ Object.defineProperty(this, "logStore", {
120
+ enumerable: true,
121
+ configurable: true,
122
+ writable: true,
123
+ value: null
124
+ });
115
125
  Object.defineProperty(this, "vendors", {
116
126
  enumerable: true,
117
127
  configurable: true,
@@ -143,48 +153,6 @@ class FileSystemDatabaseManager {
143
153
  writable: true,
144
154
  value: []
145
155
  });
146
- Object.defineProperty(this, "logShardsIndex", {
147
- enumerable: true,
148
- configurable: true,
149
- writable: true,
150
- value: []
151
- });
152
- Object.defineProperty(this, "sessionLogIndex", {
153
- enumerable: true,
154
- configurable: true,
155
- writable: true,
156
- value: new Map()
157
- });
158
- Object.defineProperty(this, "sessionLogIndexDirty", {
159
- enumerable: true,
160
- configurable: true,
161
- writable: true,
162
- value: false
163
- });
164
- Object.defineProperty(this, "sessionLogIndexDirtyCount", {
165
- enumerable: true,
166
- configurable: true,
167
- writable: true,
168
- value: 0
169
- });
170
- Object.defineProperty(this, "sessionLogIndexFlushTimer", {
171
- enumerable: true,
172
- configurable: true,
173
- writable: true,
174
- value: null
175
- });
176
- Object.defineProperty(this, "SESSION_LOG_INDEX_FLUSH_DELAY", {
177
- enumerable: true,
178
- configurable: true,
179
- writable: true,
180
- value: 3000
181
- });
182
- Object.defineProperty(this, "SESSION_LOG_INDEX_FLUSH_THRESHOLD", {
183
- enumerable: true,
184
- configurable: true,
185
- writable: true,
186
- value: 50
187
- });
188
156
  Object.defineProperty(this, "errorLogs", {
189
157
  enumerable: true,
190
158
  configurable: true,
@@ -243,26 +211,6 @@ class FileSystemDatabaseManager {
243
211
  writable: true,
244
212
  value: false
245
213
  });
246
- // 分片写入锁:防止并发读写同一个分片文件导致数据损坏
247
- Object.defineProperty(this, "shardWriteLocks", {
248
- enumerable: true,
249
- configurable: true,
250
- writable: true,
251
- value: new Map()
252
- });
253
- // 延迟维护标记:启动时跳过耗时操作,后台异步执行
254
- Object.defineProperty(this, "_needsShardVerification", {
255
- enumerable: true,
256
- configurable: true,
257
- writable: true,
258
- value: false
259
- });
260
- Object.defineProperty(this, "_needsSessionLogIndexBuild", {
261
- enumerable: true,
262
- configurable: true,
263
- writable: true,
264
- value: false
265
- });
266
214
  // 缓存机制
267
215
  Object.defineProperty(this, "logsCountCache", {
268
216
  enumerable: true,
@@ -282,13 +230,7 @@ class FileSystemDatabaseManager {
282
230
  writable: true,
283
231
  value: 1000
284
232
  });
285
- // 日志分片配置
286
- Object.defineProperty(this, "MAX_SHARD_SIZE", {
287
- enumerable: true,
288
- configurable: true,
289
- writable: true,
290
- value: 10 * 1024 * 1024
291
- }); // 10MB
233
+ // 日志保留期(委托 LogStore.retain)
292
234
  Object.defineProperty(this, "MAX_ERROR_LOG_FIELD_SIZE", {
293
235
  enumerable: true,
294
236
  configurable: true,
@@ -313,6 +255,19 @@ class FileSystemDatabaseManager {
313
255
  });
314
256
  this.dataPath = dataPath;
315
257
  }
258
+ /** 注入共享 LogStore(由 main.ts 在启动时调用)。日志存取统一委托给它。 */
259
+ setLogStore(ls) {
260
+ this.logStore = ls;
261
+ }
262
+ getLogStore() {
263
+ return this.logStore;
264
+ }
265
+ requireLogStore() {
266
+ if (!this.logStore) {
267
+ throw new Error('LogStore not initialized');
268
+ }
269
+ return this.logStore;
270
+ }
316
271
  initialize() {
317
272
  return __awaiter(this, void 0, void 0, function* () {
318
273
  // 确保数据目录存在
@@ -328,38 +283,20 @@ class FileSystemDatabaseManager {
328
283
  });
329
284
  }
330
285
  /**
331
- * 执行延迟的维护任务(启动后异步执行,不阻塞服务启动)
332
- * 包括:分片一致性校验、损坏修复、旧日志清理、会话索引构建
286
+ * 执行延迟的维护任务(启动后异步执行,不阻塞服务启动)。
287
+ * 旧数据迁移已在 main.ts 中 pre-listen 完成;这里只做保留期清理。
333
288
  */
334
289
  deferredMaintenance() {
335
290
  return __awaiter(this, void 0, void 0, function* () {
336
- const tasks = [];
337
- if (this._needsShardVerification) {
338
- this._needsShardVerification = false;
339
- tasks.push((() => __awaiter(this, void 0, void 0, function* () {
340
- try {
341
- yield this.verifyShardIndexConsistency();
342
- yield this.cleanupOldLogShards();
343
- console.log('[Database] Background shard verification completed');
344
- }
345
- catch (err) {
346
- console.error('[Database] Background shard verification failed:', err);
347
- }
348
- }))());
291
+ if (!this.logStore)
292
+ return;
293
+ try {
294
+ yield this.logStore.retain('global', this.LOG_RETENTION_DAYS);
295
+ console.log('[Database] LogStore deferred maintenance completed');
349
296
  }
350
- if (this._needsSessionLogIndexBuild) {
351
- this._needsSessionLogIndexBuild = false;
352
- tasks.push((() => __awaiter(this, void 0, void 0, function* () {
353
- try {
354
- yield this.buildSessionLogIndex();
355
- console.log('[Database] Background session log index build completed');
356
- }
357
- catch (err) {
358
- console.error('[Database] Background session log index build failed:', err);
359
- }
360
- }))());
297
+ catch (err) {
298
+ console.error('[Database] LogStore deferred maintenance failed:', err);
361
299
  }
362
- yield Promise.all(tasks);
363
300
  });
364
301
  }
365
302
  loadAllData() {
@@ -370,7 +307,6 @@ class FileSystemDatabaseManager {
370
307
  this.loadRoutes(),
371
308
  this.loadConfig(),
372
309
  this.loadSessions(),
373
- this.loadLogsIndex(true), // 启动时跳过耗时校验
374
310
  this.loadErrorLogs(),
375
311
  this.loadBlacklist(),
376
312
  this.loadStatistics(),
@@ -378,8 +314,7 @@ class FileSystemDatabaseManager {
378
314
  this.loadApiPathBindings(),
379
315
  this.loadToolBindings(),
380
316
  ]);
381
- // 会话日志索引依赖 logShardsIndex,必须在 loadLogsIndex 之后
382
- yield this.loadSessionLogIndex(true); // 启动时跳过全量构建
317
+ // 日志存取已委托给 LogStore,不再在此加载旧分片索引
383
318
  });
384
319
  }
385
320
  loadVendors() {
@@ -714,351 +649,6 @@ class FileSystemDatabaseManager {
714
649
  yield promises_1.default.writeFile(this.sessionsFile, JSON.stringify(this.sessions, null, 2));
715
650
  });
716
651
  }
717
- loadLogsIndex() {
718
- return __awaiter(this, arguments, void 0, function* (deferMaintenance = true) {
719
- try {
720
- const data = yield promises_1.default.readFile(this.logsIndexFile, 'utf-8');
721
- this.logShardsIndex = JSON.parse(data);
722
- }
723
- catch (_a) {
724
- this.logShardsIndex = [];
725
- yield this.saveLogsIndex();
726
- }
727
- // 检查并迁移旧的 logs.json 文件
728
- yield this.migrateOldLogsIfNeeded();
729
- if (deferMaintenance) {
730
- // 启动时跳过耗时操作,由 deferredMaintenance() 异步执行
731
- this._needsShardVerification = true;
732
- }
733
- else {
734
- // 校验索引与实际分片数据的一致性
735
- yield this.verifyShardIndexConsistency();
736
- // 清理旧日志分片
737
- yield this.cleanupOldLogShards();
738
- }
739
- });
740
- }
741
- /**
742
- * 校验索引中的 count 与实际分片文件中的数据是否一致
743
- * 修复因并发写入竞争导致的不一致
744
- */
745
- verifyShardIndexConsistency() {
746
- return __awaiter(this, void 0, void 0, function* () {
747
- let fixedCount = 0;
748
- for (const shard of this.logShardsIndex) {
749
- try {
750
- const shardLogs = yield this.loadLogShard(shard.filename);
751
- if (shardLogs.length !== shard.count) {
752
- console.warn(`[Database] Shard count mismatch on startup: ${shard.filename} index=${shard.count} actual=${shardLogs.length}, correcting`);
753
- shard.count = shardLogs.length;
754
- fixedCount++;
755
- }
756
- }
757
- catch (_a) {
758
- // 分片文件无法读取,将 count 设为 0
759
- if (shard.count > 0) {
760
- console.warn(`[Database] Shard file unreadable on startup: ${shard.filename}, setting count to 0`);
761
- shard.count = 0;
762
- fixedCount++;
763
- }
764
- }
765
- }
766
- if (fixedCount > 0) {
767
- console.log(`[Database] Fixed ${fixedCount} shard index count mismatch(es)`);
768
- yield this.saveLogsIndex();
769
- }
770
- });
771
- }
772
- saveLogsIndex() {
773
- return __awaiter(this, void 0, void 0, function* () {
774
- yield promises_1.default.writeFile(this.logsIndexFile, JSON.stringify(this.logShardsIndex, null, 2));
775
- });
776
- }
777
- /**
778
- * 加载会话日志索引,若不存在则标记为延迟构建
779
- */
780
- loadSessionLogIndex() {
781
- return __awaiter(this, arguments, void 0, function* (deferBuild = true) {
782
- try {
783
- const data = yield promises_1.default.readFile(this.sessionLogIndexFile, 'utf-8');
784
- const parsed = JSON.parse(data);
785
- this.sessionLogIndex = new Map(Object.entries(parsed));
786
- console.log(`[Database] Session log index loaded: ${this.sessionLogIndex.size} sessions`);
787
- }
788
- catch (_a) {
789
- if (deferBuild) {
790
- // 启动时跳过全量构建,由 deferredMaintenance() 异步执行
791
- console.log('[Database] Session log index not found, will build in background...');
792
- this._needsSessionLogIndexBuild = true;
793
- }
794
- else {
795
- // 索引文件不存在,从现有日志全量构建
796
- console.log('[Database] Session log index not found, building from existing logs...');
797
- yield this.buildSessionLogIndex();
798
- }
799
- }
800
- });
801
- }
802
- /**
803
- * 从所有现有日志分片全量构建会话日志索引(首次启动或迁移用)
804
- */
805
- buildSessionLogIndex() {
806
- return __awaiter(this, void 0, void 0, function* () {
807
- this.sessionLogIndex.clear();
808
- for (const shard of this.logShardsIndex) {
809
- const shardLogs = yield this.loadLogShard(shard.filename);
810
- for (let i = 0; i < shardLogs.length; i++) {
811
- const sessionId = this.extractSessionIdFromLog(shardLogs[i]);
812
- if (sessionId) {
813
- let refs = this.sessionLogIndex.get(sessionId);
814
- if (!refs) {
815
- refs = [];
816
- this.sessionLogIndex.set(sessionId, refs);
817
- }
818
- refs.push({ filename: shard.filename, index: i, timestamp: shardLogs[i].timestamp });
819
- }
820
- }
821
- }
822
- if (this.sessionLogIndex.size > 0) {
823
- yield this.saveSessionLogIndexNow();
824
- console.log(`[Database] Session log index built: ${this.sessionLogIndex.size} sessions indexed`);
825
- }
826
- });
827
- }
828
- /**
829
- * 将会话日志索引写入磁盘
830
- */
831
- saveSessionLogIndexNow() {
832
- return __awaiter(this, void 0, void 0, function* () {
833
- const obj = {};
834
- for (const [key, refs] of this.sessionLogIndex) {
835
- obj[key] = refs;
836
- }
837
- yield promises_1.default.writeFile(this.sessionLogIndexFile, JSON.stringify(obj));
838
- });
839
- }
840
- /**
841
- * 标记索引脏数据,触发防抖写盘
842
- */
843
- scheduleSessionLogIndexFlush() {
844
- this.sessionLogIndexDirty = true;
845
- this.sessionLogIndexDirtyCount++;
846
- // 达到阈值立即刷盘
847
- if (this.sessionLogIndexDirtyCount >= this.SESSION_LOG_INDEX_FLUSH_THRESHOLD) {
848
- this.flushSessionLogIndex();
849
- return;
850
- }
851
- // 防抖定时器
852
- if (!this.sessionLogIndexFlushTimer) {
853
- this.sessionLogIndexFlushTimer = setTimeout(() => {
854
- this.flushSessionLogIndex();
855
- }, this.SESSION_LOG_INDEX_FLUSH_DELAY);
856
- }
857
- }
858
- /**
859
- * 立即将索引刷盘(关闭时调用)
860
- */
861
- flushSessionLogIndex() {
862
- if (this.sessionLogIndexFlushTimer) {
863
- clearTimeout(this.sessionLogIndexFlushTimer);
864
- this.sessionLogIndexFlushTimer = null;
865
- }
866
- if (this.sessionLogIndexDirty) {
867
- this.sessionLogIndexDirty = false;
868
- this.sessionLogIndexDirtyCount = 0;
869
- this.saveSessionLogIndexNow().catch(err => {
870
- console.error('[Database] Failed to flush session log index:', err);
871
- });
872
- }
873
- }
874
- /**
875
- * 迁移旧的 logs.json 文件到新的分片格式
876
- */
877
- migrateOldLogsIfNeeded() {
878
- return __awaiter(this, void 0, void 0, function* () {
879
- const oldLogsFile = path_1.default.join(this.dataPath, 'logs.json');
880
- try {
881
- // 检查旧日志文件是否存在
882
- yield promises_1.default.access(oldLogsFile);
883
- console.log('[Database] Found old logs.json file, migrating to shard format...');
884
- // 读取旧日志
885
- const data = yield promises_1.default.readFile(oldLogsFile, 'utf-8');
886
- const oldLogs = JSON.parse(data);
887
- if (oldLogs.length === 0) {
888
- console.log('[Database] Old logs.json is empty, skipping migration');
889
- yield promises_1.default.unlink(oldLogsFile); // 删除空文件
890
- return;
891
- }
892
- console.log(`[Database] Migrating ${oldLogs.length} log entries...`);
893
- // 按日期分组日志
894
- const logsByDate = new Map();
895
- for (const log of oldLogs) {
896
- const date = new Date(log.timestamp).toISOString().split('T')[0];
897
- if (!logsByDate.has(date)) {
898
- logsByDate.set(date, []);
899
- }
900
- logsByDate.get(date).push(log);
901
- }
902
- // 为每个日期创建分片
903
- let migratedCount = 0;
904
- for (const [date, logs] of logsByDate.entries()) {
905
- // 如果单日日志超过大小限制,需要进一步分片
906
- let currentShardLogs = [];
907
- let currentShardSize = 0;
908
- let shardIndex = 0;
909
- for (const log of logs) {
910
- const logSize = JSON.stringify(log).length;
911
- // 检查是否需要创建新分片
912
- if (currentShardSize + logSize > this.MAX_SHARD_SIZE && currentShardLogs.length > 0) {
913
- // 保存当前分片
914
- const filename = shardIndex === 0 ? `logs-${date}.json` : `logs-${date}-${shardIndex}.json`;
915
- yield this.saveLogShard(filename, currentShardLogs);
916
- // 更新索引
917
- const timestamps = currentShardLogs.map(l => l.timestamp);
918
- this.logShardsIndex.push({
919
- filename,
920
- date,
921
- startTime: Math.min(...timestamps),
922
- endTime: Math.max(...timestamps),
923
- count: currentShardLogs.length
924
- });
925
- migratedCount += currentShardLogs.length;
926
- currentShardLogs = [];
927
- currentShardSize = 0;
928
- shardIndex++;
929
- }
930
- currentShardLogs.push(log);
931
- currentShardSize += logSize;
932
- }
933
- // 保存最后一个分片
934
- if (currentShardLogs.length > 0) {
935
- const filename = shardIndex === 0 ? `logs-${date}.json` : `logs-${date}-${shardIndex}.json`;
936
- yield this.saveLogShard(filename, currentShardLogs);
937
- const timestamps = currentShardLogs.map(l => l.timestamp);
938
- this.logShardsIndex.push({
939
- filename,
940
- date,
941
- startTime: Math.min(...timestamps),
942
- endTime: Math.max(...timestamps),
943
- count: currentShardLogs.length
944
- });
945
- migratedCount += currentShardLogs.length;
946
- }
947
- }
948
- // 保存索引
949
- yield this.saveLogsIndex();
950
- console.log(`[Database] Successfully migrated ${migratedCount} log entries to ${this.logShardsIndex.length} shard(s)`);
951
- }
952
- catch (err) {
953
- if (err.code === 'ENOENT') {
954
- // 旧文件不存在,这是正常的
955
- return;
956
- }
957
- console.error('[Database] Error migrating old logs:', err);
958
- }
959
- });
960
- }
961
- cleanupOldLogShards() {
962
- return __awaiter(this, void 0, void 0, function* () {
963
- const now = Date.now();
964
- const cutoffTime = now - this.LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;
965
- // 找出需要删除的分片
966
- const toDelete = [];
967
- for (const shard of this.logShardsIndex) {
968
- if (shard.endTime < cutoffTime) {
969
- toDelete.push(shard.filename);
970
- }
971
- }
972
- // 删除旧分片文件
973
- for (const filename of toDelete) {
974
- try {
975
- const filepath = path_1.default.join(this.logsDir, filename);
976
- yield promises_1.default.unlink(filepath);
977
- }
978
- catch (err) {
979
- console.error(`Failed to delete old log shard ${filename}:`, err);
980
- }
981
- }
982
- // 更新索引
983
- this.logShardsIndex = this.logShardsIndex.filter(s => !toDelete.includes(s.filename));
984
- if (toDelete.length > 0) {
985
- yield this.saveLogsIndex();
986
- // 同步清理会话日志索引中被删除分片的条目
987
- const deleteSet = new Set(toDelete);
988
- for (const [sid, refs] of this.sessionLogIndex) {
989
- const remaining = refs.filter(r => !deleteSet.has(r.filename));
990
- if (remaining.length === 0) {
991
- this.sessionLogIndex.delete(sid);
992
- }
993
- else if (remaining.length < refs.length) {
994
- this.sessionLogIndex.set(sid, remaining);
995
- }
996
- }
997
- yield this.saveSessionLogIndexNow();
998
- }
999
- });
1000
- }
1001
- getLogShardFilename(timestamp) {
1002
- return __awaiter(this, void 0, void 0, function* () {
1003
- const date = new Date(timestamp);
1004
- const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
1005
- // 查找当天的现有分片
1006
- const existingShards = this.logShardsIndex.filter(s => s.date === dateStr);
1007
- // 检查最后一个分片的大小
1008
- for (const shard of existingShards.reverse()) {
1009
- const filepath = path_1.default.join(this.logsDir, shard.filename);
1010
- try {
1011
- const stats = yield promises_1.default.stat(filepath);
1012
- if (stats.size < this.MAX_SHARD_SIZE) {
1013
- return shard.filename;
1014
- }
1015
- }
1016
- catch (_a) {
1017
- // 文件不存在,继续查找
1018
- continue;
1019
- }
1020
- }
1021
- // 创建新分片
1022
- const shardIndex = existingShards.length;
1023
- const filename = shardIndex === 0 ? `logs-${dateStr}.json` : `logs-${dateStr}-${shardIndex}.json`;
1024
- return filename;
1025
- });
1026
- }
1027
- loadLogShard(filename) {
1028
- return __awaiter(this, void 0, void 0, function* () {
1029
- const filepath = path_1.default.join(this.logsDir, filename);
1030
- try {
1031
- let content = yield promises_1.default.readFile(filepath, 'utf-8');
1032
- // 检测并修复空字节损坏(并发写入竞争可能导致文件中间出现大量 \x00)
1033
- const nullIndex = content.indexOf('\x00');
1034
- if (nullIndex !== -1) {
1035
- console.warn(`[Database] Detected corrupted shard file: ${filename}, truncating at null byte (pos ${nullIndex})`);
1036
- content = content.substring(0, nullIndex);
1037
- // 尝试补全被截断的 JSON 数组
1038
- const openBrackets = (content.match(/\[/g) || []).length;
1039
- const closeBrackets = (content.match(/\]/g) || []).length;
1040
- if (openBrackets > closeBrackets) {
1041
- content += ']';
1042
- }
1043
- }
1044
- return JSON.parse(content);
1045
- }
1046
- catch (_a) {
1047
- return [];
1048
- }
1049
- });
1050
- }
1051
- saveLogShard(filename, logs) {
1052
- return __awaiter(this, void 0, void 0, function* () {
1053
- const filepath = path_1.default.join(this.logsDir, filename);
1054
- const tempFile = path_1.default.join(this.logsDir, `.tmp_${filename}`);
1055
- yield promises_1.default.mkdir(this.logsDir, { recursive: true });
1056
- const content = JSON.stringify(logs, null, 2);
1057
- // 先写临时文件,再原子重命名,避免写入中途被并发操作导致文件损坏
1058
- yield promises_1.default.writeFile(tempFile, content, 'utf-8');
1059
- yield promises_1.default.rename(tempFile, filepath);
1060
- });
1061
- }
1062
652
  loadErrorLogs() {
1063
653
  return __awaiter(this, void 0, void 0, function* () {
1064
654
  try {
@@ -1860,125 +1450,28 @@ class FileSystemDatabaseManager {
1860
1450
  return true;
1861
1451
  });
1862
1452
  }
1863
- // Log operations
1453
+ // Log operations —— 委托给 LogStore(追加写 NDJSON + 字节偏移索引)
1864
1454
  addLog(log) {
1865
1455
  return __awaiter(this, void 0, void 0, function* () {
1866
1456
  const id = crypto_1.default.randomUUID();
1867
1457
  const contentType = this.resolveLogContentType(log);
1868
1458
  const logWithId = Object.assign(Object.assign({}, log), { contentType, id });
1869
- // 获取目标分片文件名
1870
- const filename = yield this.getLogShardFilename(logWithId.timestamp);
1871
- // 使用分片级别的写入锁,防止并发 read-modify-write 竞争条件
1872
- const previousLock = this.shardWriteLocks.get(filename) || Promise.resolve();
1873
- let shardLogsLength = 0;
1874
- const currentWrite = previousLock.then(() => __awaiter(this, void 0, void 0, function* () {
1875
- const shardLogs = yield this.loadLogShard(filename);
1876
- shardLogs.push(logWithId);
1877
- shardLogsLength = shardLogs.length;
1878
- yield this.saveLogShard(filename, shardLogs);
1879
- // 更新索引(在锁内完成,保证一致性)
1880
- const date = new Date(logWithId.timestamp).toISOString().split('T')[0];
1881
- const shardIndex = this.logShardsIndex.find(s => s.filename === filename);
1882
- if (shardIndex) {
1883
- shardIndex.count = shardLogs.length;
1884
- shardIndex.endTime = Math.max(shardIndex.endTime, logWithId.timestamp);
1885
- }
1886
- else {
1887
- this.logShardsIndex.push({
1888
- filename,
1889
- date,
1890
- startTime: logWithId.timestamp,
1891
- endTime: logWithId.timestamp,
1892
- count: 1
1893
- });
1894
- }
1895
- yield this.saveLogsIndex();
1896
- }));
1897
- this.shardWriteLocks.set(filename, currentWrite);
1898
- try {
1899
- yield currentWrite;
1900
- }
1901
- catch (error) {
1902
- // 写入失败时清理锁
1903
- if (this.shardWriteLocks.get(filename) === currentWrite) {
1904
- this.shardWriteLocks.delete(filename);
1905
- }
1906
- throw error;
1907
- }
1908
- // 如果当前锁已执行完毕且没有被后续锁覆盖,清理它
1909
- if (this.shardWriteLocks.get(filename) === currentWrite) {
1910
- this.shardWriteLocks.delete(filename);
1911
- }
1912
- // 同时更新统计数据
1459
+ // 追加写:O(单条),不再 read-modify-write 整个分片
1460
+ yield this.requireLogStore().append('global', logWithId);
1461
+ // 统计保持写时增量(现状)
1913
1462
  yield this.updateStatistics(logWithId);
1914
1463
  // 清除计数缓存
1915
1464
  this.logsCountCache = null;
1916
- // 更新会话日志索引
1917
- const sessionId = this.extractSessionIdFromLog(logWithId);
1918
- if (sessionId) {
1919
- let refs = this.sessionLogIndex.get(sessionId);
1920
- if (!refs) {
1921
- refs = [];
1922
- this.sessionLogIndex.set(sessionId, refs);
1923
- }
1924
- refs.push({ filename, index: shardLogsLength - 1, timestamp: logWithId.timestamp });
1925
- this.scheduleSessionLogIndexFlush();
1926
- }
1927
1465
  });
1928
1466
  }
1929
1467
  getLogs() {
1930
1468
  return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
1931
- // 按分片索引倒序排列(最新的在前)
1932
- const sortedShards = [...this.logShardsIndex].sort((a, b) => b.endTime - a.endTime);
1933
- const allLogs = [];
1934
- let currentOffset = 0;
1935
- // 遍历分片直到收集足够的日志
1936
- for (const shard of sortedShards) {
1937
- if (currentOffset + shard.count <= offset) {
1938
- // 跳过整个分片(使用索引中的 count 做快速跳过判断,避免不必要的磁盘IO)
1939
- currentOffset += shard.count;
1940
- continue;
1941
- }
1942
- const shardLogs = yield this.loadLogShard(shard.filename);
1943
- // 修正索引中的计数(如果发现不一致)
1944
- if (shardLogs.length !== shard.count) {
1945
- console.warn(`[Database] Shard count mismatch: ${shard.filename} index=${shard.count} actual=${shardLogs.length}, correcting`);
1946
- shard.count = shardLogs.length;
1947
- }
1948
- // 计算需要从该分片取出的日志范围
1949
- let startIndex = 0;
1950
- if (currentOffset < offset) {
1951
- startIndex = offset - currentOffset;
1952
- }
1953
- const remainingCount = limit - allLogs.length;
1954
- const endIndex = Math.min(startIndex + remainingCount, shardLogs.length);
1955
- // 添加日志到结果
1956
- allLogs.push(...shardLogs.slice(startIndex, endIndex));
1957
- currentOffset += shardLogs.length;
1958
- if (allLogs.length >= limit) {
1959
- break;
1960
- }
1961
- }
1962
- // 按时间戳倒序排序
1963
- return allLogs.sort((a, b) => b.timestamp - a.timestamp);
1469
+ return this.requireLogStore().getRecent('global', { limit, offset });
1964
1470
  });
1965
1471
  }
1966
1472
  clearLogs() {
1967
1473
  return __awaiter(this, void 0, void 0, function* () {
1968
- // 删除所有日志分片文件
1969
- for (const shard of this.logShardsIndex) {
1970
- try {
1971
- const filepath = path_1.default.join(this.logsDir, shard.filename);
1972
- yield promises_1.default.unlink(filepath);
1973
- }
1974
- catch (err) {
1975
- console.error(`Failed to delete log shard ${shard.filename}:`, err);
1976
- }
1977
- }
1978
- // 清空索引
1979
- this.logShardsIndex = [];
1980
- yield this.saveLogsIndex();
1981
- // 清除计数缓存
1474
+ yield this.requireLogStore().clear('global');
1982
1475
  this.logsCountCache = null;
1983
1476
  });
1984
1477
  }
@@ -1988,105 +1481,35 @@ class FileSystemDatabaseManager {
1988
1481
  if (this.logsCountCache && now - this.logsCountCache.timestamp < this.CACHE_TTL) {
1989
1482
  return this.logsCountCache.count;
1990
1483
  }
1991
- const count = this.logShardsIndex.reduce((sum, shard) => sum + shard.count, 0);
1484
+ const count = yield this.requireLogStore().count('global');
1992
1485
  this.logsCountCache = { count, timestamp: now };
1993
1486
  return count;
1994
1487
  });
1995
1488
  }
1996
1489
  /**
1997
- * 搜索请求日志内容
1998
- * @param query 搜索关键词
1999
- * @param limit 返回��量限制
2000
- * @param offset 偏移量
2001
- * @returns 匹配的日志列表
1490
+ * 搜索请求日志内容(两阶段流式扫描,内存仅持描述符 + 当前页)
2002
1491
  */
2003
1492
  searchLogs(query_1) {
2004
1493
  return __awaiter(this, arguments, void 0, function* (query, limit = 100, offset = 0) {
2005
- const searchQuery = query.toLowerCase().trim();
2006
- if (!searchQuery) {
2007
- return this.getLogs(limit, offset);
2008
- }
2009
- const allMatches = [];
2010
- const sortedShards = [...this.logShardsIndex].sort((a, b) => b.endTime - a.endTime);
2011
- // 遍历所有分片进行搜索
2012
- for (const shard of sortedShards) {
2013
- const shardLogs = yield this.loadLogShard(shard.filename);
2014
- for (const log of shardLogs) {
2015
- if (this.logMatchesQuery(log, searchQuery)) {
2016
- allMatches.push(log);
2017
- }
2018
- }
2019
- }
2020
- // 按时间倒序排列并分页
2021
- return allMatches
2022
- .sort((a, b) => b.timestamp - a.timestamp)
2023
- .slice(offset, offset + limit);
1494
+ return this.requireLogStore().search('global', query, { limit, offset });
2024
1495
  });
2025
1496
  }
2026
1497
  /**
2027
- * 搜索请求日志内容数量
2028
- * @param query 搜索关键词
2029
- * @returns 匹配的日志数量
1498
+ * 搜索请求日志内容数量(流式,不持正文)
2030
1499
  */
2031
1500
  searchLogsCount(query) {
2032
1501
  return __awaiter(this, void 0, void 0, function* () {
2033
- const searchQuery = query.toLowerCase().trim();
2034
- if (!searchQuery) {
2035
- return this.getLogsCount();
2036
- }
2037
- let count = 0;
2038
- for (const shard of this.logShardsIndex) {
2039
- const shardLogs = yield this.loadLogShard(shard.filename);
2040
- for (const log of shardLogs) {
2041
- if (this.logMatchesQuery(log, searchQuery)) {
2042
- count++;
2043
- }
2044
- }
2045
- }
2046
- return count;
1502
+ return this.requireLogStore().searchCount('global', query);
2047
1503
  });
2048
1504
  }
2049
1505
  /**
2050
- * 检查日志是否匹配搜索查询
1506
+ * 统一日志查询:字段筛选 + 关键词 + 分页 + 全量命中总数。
1507
+ * 无关键词时走时间线索引(零扫描);有关键词时回退扫描。
2051
1508
  */
2052
- logMatchesQuery(log, query) {
2053
- // 搜索请求体
2054
- if (log.body) {
2055
- const bodyStr = typeof log.body === 'string' ? log.body : JSON.stringify(log.body);
2056
- if (bodyStr.toLowerCase().includes(query)) {
2057
- return true;
2058
- }
2059
- }
2060
- // 搜索响应体
2061
- if (log.responseBody && typeof log.responseBody === 'string') {
2062
- if (log.responseBody.toLowerCase().includes(query)) {
2063
- return true;
2064
- }
2065
- }
2066
- // 搜索流式响应块
2067
- if (log.streamChunks && log.streamChunks.length > 0) {
2068
- for (const chunk of log.streamChunks) {
2069
- if (chunk.toLowerCase().includes(query)) {
2070
- return true;
2071
- }
2072
- }
2073
- }
2074
- // 搜索错误信息
2075
- if (log.error && log.error.toLowerCase().includes(query)) {
2076
- return true;
2077
- }
2078
- // 搜索路径
2079
- if (log.path && log.path.toLowerCase().includes(query)) {
2080
- return true;
2081
- }
2082
- // 搜索模型名称
2083
- if (log.requestModel && log.requestModel.toLowerCase().includes(query)) {
2084
- return true;
2085
- }
2086
- if (log.targetModel && log.targetModel.toLowerCase().includes(query)) {
2087
- return true;
2088
- }
2089
- return false;
1509
+ queryLogs(opts) {
1510
+ return __awaiter(this, void 0, void 0, function* () {
1511
+ return this.requireLogStore().query('global', opts);
1512
+ });
2090
1513
  }
2091
1514
  truncateForErrorLog(value) {
2092
1515
  if (value === undefined || value === null)
@@ -2128,6 +1551,36 @@ class FileSystemDatabaseManager {
2128
1551
  return count;
2129
1552
  });
2130
1553
  }
1554
+ /**
1555
+ * 统一错误日志查询:字段筛选 + 关键词 + 分页 + 全量命中总数。
1556
+ * 错误日志常驻内存,纯内存过滤。
1557
+ */
1558
+ queryErrorLogs(opts) {
1559
+ return __awaiter(this, void 0, void 0, function* () {
1560
+ const filters = opts.filters;
1561
+ const keyword = (opts.keyword || '').toLowerCase().trim();
1562
+ const filtered = this.errorLogs.filter(log => {
1563
+ if ((filters === null || filters === void 0 ? void 0 : filters.targetType) && log.targetType !== filters.targetType)
1564
+ return false;
1565
+ if ((filters === null || filters === void 0 ? void 0 : filters.vendorId) && log.vendorId !== filters.vendorId)
1566
+ return false;
1567
+ if ((filters === null || filters === void 0 ? void 0 : filters.serviceId) && log.targetServiceId !== filters.serviceId)
1568
+ return false;
1569
+ if ((filters === null || filters === void 0 ? void 0 : filters.model) && log.targetModel !== filters.model)
1570
+ return false;
1571
+ if ((filters === null || filters === void 0 ? void 0 : filters.routeId) && log.routeId !== filters.routeId)
1572
+ return false;
1573
+ if (keyword && !this.errorLogMatchesQuery(log, keyword))
1574
+ return false;
1575
+ return true;
1576
+ });
1577
+ const sorted = filtered.sort((a, b) => b.timestamp - a.timestamp);
1578
+ return {
1579
+ data: sorted.slice(opts.offset, opts.offset + opts.limit),
1580
+ total: filtered.length,
1581
+ };
1582
+ });
1583
+ }
2131
1584
  /**
2132
1585
  * 搜索错误日志内容
2133
1586
  * @param query 搜索关键词
@@ -2200,49 +1653,6 @@ class FileSystemDatabaseManager {
2200
1653
  }
2201
1654
  return false;
2202
1655
  }
2203
- /**
2204
- * 获取状态码为 499 的请求日志
2205
- * @param limit 返回数量限制
2206
- * @param offset 偏移量
2207
- * @returns 匹配的请求日志列表
2208
- */
2209
- getClientClosedLogs() {
2210
- return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
2211
- const allMatches = [];
2212
- const sortedShards = [...this.logShardsIndex].sort((a, b) => b.endTime - a.endTime);
2213
- // 递序遍历所有分片, collect 499 logs
2214
- for (const shard of sortedShards) {
2215
- const shardLogs = yield this.loadLogShard(shard.filename);
2216
- for (const log of shardLogs) {
2217
- if (log.statusCode === 499) {
2218
- allMatches.push(log);
2219
- }
2220
- }
2221
- }
2222
- // 按时间倒序排列并分页
2223
- return allMatches
2224
- .sort((a, b) => b.timestamp - a.timestamp)
2225
- .slice(offset, offset + limit);
2226
- });
2227
- }
2228
- /**
2229
- * 获取状态码为 499 的请求日志数量
2230
- * @returns 匹配的请求数量
2231
- */
2232
- getClientClosedLogsCount() {
2233
- return __awaiter(this, void 0, void 0, function* () {
2234
- let count = 0;
2235
- for (const shard of this.logShardsIndex) {
2236
- const shardLogs = yield this.loadLogShard(shard.filename);
2237
- for (const log of shardLogs) {
2238
- if (log.statusCode === 499) {
2239
- count++;
2240
- }
2241
- }
2242
- }
2243
- return count;
2244
- });
2245
- }
2246
1656
  // Service blacklist operations
2247
1657
  // Service blacklist operations
2248
1658
  getFailoverRecoveryMs() {
@@ -2678,25 +2088,40 @@ class FileSystemDatabaseManager {
2678
2088
  }
2679
2089
  ensureContentTypeDistribution() {
2680
2090
  return __awaiter(this, void 0, void 0, function* () {
2091
+ var _a, e_1, _b, _c;
2681
2092
  if (this.contentTypeDistributionInitialized || this.contentTypeDistributionInitializing) {
2682
2093
  return;
2683
2094
  }
2684
2095
  this.contentTypeDistributionInitializing = true;
2685
2096
  try {
2686
- if (this.logShardsIndex.length === 0) {
2097
+ if (!this.logStore) {
2687
2098
  this.contentTypeDistributionInitialized = true;
2688
2099
  return;
2689
2100
  }
2690
2101
  const counts = new Map();
2691
2102
  let totalRequests = 0;
2692
- for (const shard of this.logShardsIndex) {
2693
- const shardLogs = yield this.loadLogShard(shard.filename);
2694
- for (const log of shardLogs) {
2103
+ try {
2104
+ // LogStore 流式扫描全部日志,按 contentType 计数(仅首次/老数据触发)
2105
+ for (var _d = true, _e = __asyncValues(this.logStore.streamAll('global')), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
2106
+ _c = _f.value;
2107
+ _d = false;
2108
+ const log = _c;
2695
2109
  totalRequests++;
2696
2110
  const contentType = this.resolveLogContentType(log);
2697
2111
  counts.set(contentType, (counts.get(contentType) || 0) + 1);
2698
2112
  }
2699
2113
  }
2114
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
2115
+ finally {
2116
+ try {
2117
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
2118
+ }
2119
+ finally { if (e_1) throw e_1.error; }
2120
+ }
2121
+ if (totalRequests === 0) {
2122
+ this.contentTypeDistributionInitialized = true;
2123
+ return;
2124
+ }
2700
2125
  this.statistics.contentTypeDistribution = Array.from(counts.entries()).map(([contentType, count]) => ({
2701
2126
  contentType,
2702
2127
  count,
@@ -2806,9 +2231,9 @@ class FileSystemDatabaseManager {
2806
2231
  serviceStats.avgResponseTime =
2807
2232
  (serviceStats.avgResponseTime * (serviceStats.totalRequests - 1) + responseTime) / serviceStats.totalRequests;
2808
2233
  }
2809
- // 更新 byModel
2810
- if (log.requestModel || log.targetModel) {
2811
- const modelName = log.requestModel || log.targetModel || 'Unknown';
2234
+ // 更新 byModel(按实际转发的模型统计,优先 targetModel 而非客户端提交的 requestModel)
2235
+ if (log.targetModel || log.requestModel) {
2236
+ const modelName = log.targetModel || log.requestModel || 'Unknown';
2812
2237
  let modelStats = this.statistics.byModel.find(s => s.modelName === modelName);
2813
2238
  if (!modelStats) {
2814
2239
  modelStats = { modelName, totalRequests: 0, totalTokens: 0, avgResponseTime: 0 };
@@ -2915,154 +2340,89 @@ class FileSystemDatabaseManager {
2915
2340
  return true;
2916
2341
  });
2917
2342
  }
2918
- getSessions(targetType_1) {
2919
- return __awaiter(this, arguments, void 0, function* (targetType, limit = 100, offset = 0) {
2920
- let filtered = targetType
2921
- ? this.sessions.filter(s => s.targetType === targetType)
2922
- : this.sessions;
2923
- filtered = filtered.sort((a, b) => b.lastRequestAt - a.lastRequestAt);
2343
+ /**
2344
+ * 查询会话列表。支持字段筛选(targetType/vendorId/serviceId/model/routeId)
2345
+ * 与关键词搜索(命中 title/id)。会话常驻内存,筛选为纯内存过滤。
2346
+ * 兼容旧调用:第一个参数传 undefined 等价于无筛选。
2347
+ */
2348
+ getSessions(opts_1) {
2349
+ return __awaiter(this, arguments, void 0, function* (opts, limit = 100, offset = 0) {
2350
+ const filtered = this.applySessionFilters(this.sessions, opts)
2351
+ .sort((a, b) => b.lastRequestAt - a.lastRequestAt);
2924
2352
  return filtered.slice(offset, offset + limit);
2925
2353
  });
2926
2354
  }
2927
- getSessionsCount(targetType) {
2355
+ getSessionsCount(opts) {
2928
2356
  return __awaiter(this, void 0, void 0, function* () {
2929
- if (targetType) {
2930
- return this.sessions.filter(s => s.targetType === targetType).length;
2931
- }
2932
- return this.sessions.length;
2357
+ return this.applySessionFilters(this.sessions, opts).length;
2933
2358
  });
2934
2359
  }
2935
- getLogsBySessionId(sessionId_1) {
2936
- return __awaiter(this, arguments, void 0, function* (sessionId, limit = 100) {
2937
- const refs = this.sessionLogIndex.get(sessionId);
2938
- // 有索引:仅加载相关分片,按 index 直接取值
2939
- if (refs && refs.length > 0) {
2940
- // filename 分组,避免重复加载同一分片
2941
- const shardMap = new Map();
2942
- for (const ref of refs) {
2943
- let indices = shardMap.get(ref.filename);
2944
- if (!indices) {
2945
- indices = [];
2946
- shardMap.set(ref.filename, indices);
2947
- }
2948
- indices.push(ref.index);
2949
- }
2950
- const logs = [];
2951
- for (const [filename, indices] of shardMap) {
2952
- try {
2953
- const shardLogs = yield this.loadLogShard(filename);
2954
- for (const idx of indices) {
2955
- if (idx >= 0 && idx < shardLogs.length) {
2956
- logs.push(shardLogs[idx]);
2957
- }
2958
- }
2959
- }
2960
- catch (_a) {
2961
- // 分片文件可能已被清理,跳过
2962
- }
2963
- }
2964
- return logs.sort((a, b) => b.timestamp - a.timestamp).slice(0, limit);
2965
- }
2966
- // 无索引(兼容旧数据):回退到全扫描
2967
- const allLogs = [];
2968
- for (const shard of this.logShardsIndex) {
2969
- const shardLogs = yield this.loadLogShard(shard.filename);
2970
- const filtered = shardLogs.filter(log => this.isLogBelongsToSession(log, sessionId));
2971
- allLogs.push(...filtered);
2360
+ applySessionFilters(sessions, opts) {
2361
+ var _a;
2362
+ if (!opts)
2363
+ return [...sessions];
2364
+ const keyword = (_a = opts.keyword) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
2365
+ return sessions.filter(s => {
2366
+ if (opts.targetType && s.targetType !== opts.targetType)
2367
+ return false;
2368
+ if (opts.vendorId && s.vendorId !== opts.vendorId)
2369
+ return false;
2370
+ if (opts.serviceId && s.serviceId !== opts.serviceId)
2371
+ return false;
2372
+ if (opts.model && s.model !== opts.model)
2373
+ return false;
2374
+ if (opts.routeId && s.routeId !== opts.routeId)
2375
+ return false;
2376
+ if (keyword) {
2377
+ const title = (s.title || '').toLowerCase();
2378
+ const id = (s.id || '').toLowerCase();
2379
+ if (!title.includes(keyword) && !id.includes(keyword))
2380
+ return false;
2972
2381
  }
2973
- return allLogs.sort((a, b) => b.timestamp - a.timestamp).slice(0, limit);
2382
+ return true;
2974
2383
  });
2975
2384
  }
2976
2385
  /**
2977
- * 检查日志是否属于指定 session
2386
+ * refs 回填日志正文。按 filename 分组,每个分片只 loadLogShard 一次,
2387
+ * 处理完即释放分片引用,避免大量分片正文同时驻留内存。
2388
+ * 供 getLogsBySessionId / searchLogs / getClientClosedLogs 复用。
2978
2389
  */
2979
- isLogBelongsToSession(log, sessionId) {
2980
- var _a, _b, _c;
2981
- // 检查 headers 中的 session-id session_id(Codex,兼容新旧版本)
2982
- if (((_a = log.headers) === null || _a === void 0 ? void 0 : _a['session-id']) === sessionId || ((_b = log.headers) === null || _b === void 0 ? void 0 : _b['session_id']) === sessionId) {
2983
- return true;
2984
- }
2985
- // 检查 body 中的 metadata.user_id(Claude Code)
2986
- if (log.body) {
2987
- try {
2988
- // body 可能是对象(已解析)或字符串(未解析)
2989
- const body = typeof log.body === 'string' ? JSON.parse(log.body) : log.body;
2990
- if ((_c = body.metadata) === null || _c === void 0 ? void 0 : _c.user_id) {
2991
- const userId = body.metadata.user_id;
2992
- // 兼容新旧格式:新版本为 JSON 字符串,旧版本为纯字符串
2993
- let extractedSessionId = null;
2994
- try {
2995
- const parsed = JSON.parse(userId);
2996
- if (parsed && typeof parsed === 'object' && parsed.session_id) {
2997
- extractedSessionId = parsed.session_id;
2998
- }
2999
- }
3000
- catch (_d) {
3001
- // 不是 JSON,按旧版本纯字符串处理
3002
- extractedSessionId = userId;
3003
- }
3004
- if (extractedSessionId === sessionId) {
3005
- return true;
3006
- }
3007
- }
3008
- }
3009
- catch (_e) {
3010
- // 忽略解析错误
3011
- }
3012
- }
3013
- return false;
2390
+ /** 按会话取日志(字节偏移随机读,since 下推到索引层)。委托 LogStore。 */
2391
+ getLogsBySessionId(sessionId_1) {
2392
+ return __awaiter(this, arguments, void 0, function* (sessionId, limit = 100, since) {
2393
+ return this.requireLogStore().getBySession('global', sessionId, { limit, since });
2394
+ });
3014
2395
  }
3015
2396
  /**
3016
- * 从日志条目中提取 sessionId(用于索引)
3017
- * Codex: headers['session-id'](新版)或 headers['session_id'](旧版)
3018
- * Claude Code: body.metadata.user_id(兼容新旧格式)
2397
+ * 批量回填多个会话的近期日志(跨会话合并文件读取),委托 LogStore。
2398
+ * 用于 Agent Map 启动重建。
3019
2399
  */
3020
- extractSessionIdFromLog(log) {
3021
- var _a, _b, _c;
3022
- // Codex: headers 中的 session-id 或 session_id(兼容新旧版本)
3023
- const headerSessionId = ((_a = log.headers) === null || _a === void 0 ? void 0 : _a['session-id']) || ((_b = log.headers) === null || _b === void 0 ? void 0 : _b['session_id']);
3024
- if (typeof headerSessionId === 'string')
3025
- return headerSessionId;
3026
- // Claude Code: body 中的 metadata.user_id
3027
- if (log.body) {
3028
- try {
3029
- const body = typeof log.body === 'string' ? JSON.parse(log.body) : log.body;
3030
- if ((_c = body.metadata) === null || _c === void 0 ? void 0 : _c.user_id) {
3031
- const userId = body.metadata.user_id;
3032
- try {
3033
- const parsed = JSON.parse(userId);
3034
- if (parsed && typeof parsed === 'object' && parsed.session_id) {
3035
- return parsed.session_id;
3036
- }
3037
- }
3038
- catch (_d) {
3039
- return userId;
3040
- }
3041
- }
3042
- }
3043
- catch (_e) {
3044
- // 忽略解析错误
3045
- }
3046
- }
3047
- return null;
2400
+ getRecentLogsBySessions(sessionIds_1) {
2401
+ return __awaiter(this, arguments, void 0, function* (sessionIds, opts = {}) {
2402
+ return this.requireLogStore().getBySessionsBatch('global', sessionIds, opts);
2403
+ });
3048
2404
  }
2405
+ // sessionId 提取已移至 LogStore 内部(extractSessionId),fs-database 不再维护会话日志索引
3049
2406
  deleteSession(sessionId) {
3050
2407
  return __awaiter(this, void 0, void 0, function* () {
3051
2408
  const index = this.sessions.findIndex(s => s.id === sessionId);
3052
2409
  if (index === -1)
3053
2410
  return false;
3054
2411
  this.sessions.splice(index, 1);
3055
- this.sessionLogIndex.delete(sessionId);
3056
- this.scheduleSessionLogIndexFlush();
3057
2412
  yield this.saveSessions();
2413
+ // 关联日志删除走 LogStore tombstone
2414
+ try {
2415
+ yield this.requireLogStore().deleteLogsBySession('global', sessionId);
2416
+ }
2417
+ catch (err) {
2418
+ console.error('[Database] deleteLogsBySession failed:', err);
2419
+ }
3058
2420
  return true;
3059
2421
  });
3060
2422
  }
3061
2423
  clearSessions() {
3062
2424
  return __awaiter(this, void 0, void 0, function* () {
3063
2425
  this.sessions = [];
3064
- this.sessionLogIndex.clear();
3065
- this.scheduleSessionLogIndexFlush();
3066
2426
  yield this.saveSessions();
3067
2427
  });
3068
2428
  }
@@ -3089,140 +2449,18 @@ class FileSystemDatabaseManager {
3089
2449
  });
3090
2450
  }
3091
2451
  /**
3092
- * 删除指定会话集合关联的所有日志条目,并维护分片文件与索引的一致性。
3093
- * 多个会话可能共享同一分片,按分片重写并修正其余会话的 index 引用。
3094
- * 与 addLog 共享分片写入锁(shardWriteLocks),避免并发写入竞争。
2452
+ * 删除指定会话集合关联的所有日志条目(tombstone,追加写无需重写文件)。
2453
+ * 委托 LogStore。
3095
2454
  */
3096
2455
  deleteLogsBySessionIds(sessionIds) {
3097
2456
  return __awaiter(this, void 0, void 0, function* () {
3098
2457
  if (sessionIds.size === 0)
3099
2458
  return { logsDeleted: 0 };
3100
- // 1. 按分片文件收集待删除的 index(基于内存索引快照)
3101
- const shardDeletes = new Map();
3102
- for (const sid of sessionIds) {
3103
- const refs = this.sessionLogIndex.get(sid);
3104
- if (!refs)
3105
- continue;
3106
- for (const ref of refs) {
3107
- let set = shardDeletes.get(ref.filename);
3108
- if (!set) {
3109
- set = new Set();
3110
- shardDeletes.set(ref.filename, set);
3111
- }
3112
- set.add(ref.index);
3113
- }
3114
- }
2459
+ const ls = this.requireLogStore();
3115
2460
  let logsDeleted = 0;
3116
- // 2. 逐分片重写(在分片锁内执行,与该分片的 addLog 串行)
3117
- for (const [filename, deleteSet] of shardDeletes) {
3118
- let shardRemoved = 0;
3119
- const prevLock = this.shardWriteLocks.get(filename) || Promise.resolve();
3120
- const task = prevLock.then(() => __awaiter(this, void 0, void 0, function* () {
3121
- const shardLogs = yield this.loadLogShard(filename);
3122
- if (shardLogs.length === 0)
3123
- return;
3124
- // 构建 oldIndex -> newIndex 映射(仅保留项)
3125
- const newIndexMap = new Map();
3126
- const newLogs = [];
3127
- let removed = 0;
3128
- for (let i = 0; i < shardLogs.length; i++) {
3129
- if (deleteSet.has(i)) {
3130
- removed++;
3131
- continue;
3132
- }
3133
- newIndexMap.set(i, newLogs.length);
3134
- newLogs.push(shardLogs[i]);
3135
- }
3136
- if (removed === 0)
3137
- return;
3138
- shardRemoved = removed;
3139
- // 重写或删除分片文件
3140
- if (newLogs.length === 0) {
3141
- try {
3142
- yield promises_1.default.unlink(path_1.default.join(this.logsDir, filename));
3143
- }
3144
- catch (_a) {
3145
- // 文件可能已被其它清理流程删除,忽略
3146
- }
3147
- }
3148
- else {
3149
- yield this.saveLogShard(filename, newLogs);
3150
- }
3151
- // 更新分片索引
3152
- const shardIndex = this.logShardsIndex.find(s => s.filename === filename);
3153
- if (shardIndex) {
3154
- if (newLogs.length === 0) {
3155
- this.logShardsIndex = this.logShardsIndex.filter(s => s.filename !== filename);
3156
- }
3157
- else {
3158
- shardIndex.count = newLogs.length;
3159
- let minTs = Infinity;
3160
- let maxTs = -Infinity;
3161
- for (const l of newLogs) {
3162
- if (l.timestamp < minTs)
3163
- minTs = l.timestamp;
3164
- if (l.timestamp > maxTs)
3165
- maxTs = l.timestamp;
3166
- }
3167
- shardIndex.startTime = minTs;
3168
- shardIndex.endTime = maxTs;
3169
- }
3170
- }
3171
- // 修正引用了该分片的非目标会话的 refs(index 偏移)
3172
- for (const [sid, refs] of this.sessionLogIndex) {
3173
- if (sessionIds.has(sid))
3174
- continue;
3175
- let touched = false;
3176
- const updated = [];
3177
- for (const ref of refs) {
3178
- if (ref.filename !== filename) {
3179
- updated.push(ref);
3180
- continue;
3181
- }
3182
- if (deleteSet.has(ref.index)) {
3183
- touched = true;
3184
- continue;
3185
- }
3186
- const ni = newIndexMap.get(ref.index);
3187
- if (ni !== undefined) {
3188
- if (ni !== ref.index)
3189
- touched = true;
3190
- updated.push(Object.assign(Object.assign({}, ref), { index: ni }));
3191
- }
3192
- else {
3193
- touched = true;
3194
- }
3195
- }
3196
- if (touched) {
3197
- if (updated.length === 0) {
3198
- this.sessionLogIndex.delete(sid);
3199
- }
3200
- else {
3201
- this.sessionLogIndex.set(sid, updated);
3202
- }
3203
- }
3204
- }
3205
- }));
3206
- this.shardWriteLocks.set(filename, task);
3207
- try {
3208
- yield task;
3209
- logsDeleted += shardRemoved;
3210
- }
3211
- catch (err) {
3212
- console.error(`[Database] Failed to cleanup shard ${filename}:`, err);
3213
- }
3214
- finally {
3215
- if (this.shardWriteLocks.get(filename) === task) {
3216
- this.shardWriteLocks.delete(filename);
3217
- }
3218
- }
3219
- }
3220
- // 3. 清除目标会话的索引条目
3221
2461
  for (const sid of sessionIds) {
3222
- this.sessionLogIndex.delete(sid);
2462
+ logsDeleted += yield ls.deleteLogsBySession('global', sid);
3223
2463
  }
3224
- yield this.saveLogsIndex();
3225
- yield this.saveSessionLogIndexNow();
3226
2464
  this.logsCountCache = null;
3227
2465
  return { logsDeleted };
3228
2466
  });
@@ -3384,8 +2622,7 @@ class FileSystemDatabaseManager {
3384
2622
  }
3385
2623
  // Close method for compatibility (no-op for filesystem database)
3386
2624
  close() {
3387
- // 刷盘会话日志索引
3388
- this.flushSessionLogIndex();
2625
+ // 日志索引落盘由 LogStore.close() 负责(main.ts 在 shutdown 时调用)
3389
2626
  }
3390
2627
  }
3391
2628
  exports.FileSystemDatabaseManager = FileSystemDatabaseManager;