aicodeswitch 5.2.12 → 6.0.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/README.md +6 -2
- package/UPGRADE.md +5 -4
- package/bin/utils/get-server.js +3 -3
- package/dist/server/access-keys/index.js +2 -2
- package/dist/server/access-keys/key-logger.js +63 -309
- package/dist/server/agent-map/activity-extractor.js +383 -0
- package/dist/server/agent-map/agent-map-service.js +1011 -0
- package/dist/server/agent-map/index.js +10 -0
- package/dist/server/agent-map/routes.js +88 -0
- package/dist/server/agent-map/session-meta.js +300 -0
- package/dist/server/conversions/index.js +10 -2
- package/dist/server/conversions/thinking/providers.js +12 -7
- package/dist/server/fs-database.js +169 -926
- package/dist/server/log-store/index.js +21 -0
- package/dist/server/log-store/log-store.js +1367 -0
- package/dist/server/log-store/types.js +2 -0
- package/dist/server/main.js +141 -46
- package/dist/server/notifier.js +100 -0
- package/dist/server/performance-tracker.js +78 -8
- package/dist/server/proxy-server.js +173 -56
- package/dist/server/transformers/chunk-collector.js +112 -25
- package/dist/ui/assets/index-CQVhBlBB.css +1 -0
- package/dist/ui/assets/index-QZBX5HHX.js +1186 -0
- package/dist/ui/assets/three-CFpmPosW.js +3802 -0
- package/dist/ui/index.html +3 -2
- package/package.json +4 -2
- package/scripts/dev.js +283 -0
- package/dist/server/tools-service.js +0 -203
- package/dist/server/websocket-service.js +0 -148
- package/dist/ui/assets/index-BFVjD9Y2.js +0 -799
- package/dist/ui/assets/index-Dm34-4zP.css +0 -1
|
@@ -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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1871
|
-
//
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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.
|
|
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
|
-
|
|
2693
|
-
|
|
2694
|
-
for (
|
|
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,
|
|
@@ -2898,6 +2323,8 @@ class FileSystemDatabaseManager {
|
|
|
2898
2323
|
lastRequestAt: now,
|
|
2899
2324
|
requestCount: 1,
|
|
2900
2325
|
totalTokens: 0,
|
|
2326
|
+
inputTokens: 0,
|
|
2327
|
+
outputTokens: 0,
|
|
2901
2328
|
};
|
|
2902
2329
|
this.sessions.push(session);
|
|
2903
2330
|
yield this.saveSessions();
|
|
@@ -2915,154 +2342,89 @@ class FileSystemDatabaseManager {
|
|
|
2915
2342
|
return true;
|
|
2916
2343
|
});
|
|
2917
2344
|
}
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2345
|
+
/**
|
|
2346
|
+
* 查询会话列表。支持字段筛选(targetType/vendorId/serviceId/model/routeId)
|
|
2347
|
+
* 与关键词搜索(命中 title/id)。会话常驻内存,筛选为纯内存过滤。
|
|
2348
|
+
* 兼容旧调用:第一个参数传 undefined 等价于无筛选。
|
|
2349
|
+
*/
|
|
2350
|
+
getSessions(opts_1) {
|
|
2351
|
+
return __awaiter(this, arguments, void 0, function* (opts, limit = 100, offset = 0) {
|
|
2352
|
+
const filtered = this.applySessionFilters(this.sessions, opts)
|
|
2353
|
+
.sort((a, b) => b.lastRequestAt - a.lastRequestAt);
|
|
2924
2354
|
return filtered.slice(offset, offset + limit);
|
|
2925
2355
|
});
|
|
2926
2356
|
}
|
|
2927
|
-
getSessionsCount(
|
|
2357
|
+
getSessionsCount(opts) {
|
|
2928
2358
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2929
|
-
|
|
2930
|
-
return this.sessions.filter(s => s.targetType === targetType).length;
|
|
2931
|
-
}
|
|
2932
|
-
return this.sessions.length;
|
|
2359
|
+
return this.applySessionFilters(this.sessions, opts).length;
|
|
2933
2360
|
});
|
|
2934
2361
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
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);
|
|
2362
|
+
applySessionFilters(sessions, opts) {
|
|
2363
|
+
var _a;
|
|
2364
|
+
if (!opts)
|
|
2365
|
+
return [...sessions];
|
|
2366
|
+
const keyword = (_a = opts.keyword) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
|
|
2367
|
+
return sessions.filter(s => {
|
|
2368
|
+
if (opts.targetType && s.targetType !== opts.targetType)
|
|
2369
|
+
return false;
|
|
2370
|
+
if (opts.vendorId && s.vendorId !== opts.vendorId)
|
|
2371
|
+
return false;
|
|
2372
|
+
if (opts.serviceId && s.serviceId !== opts.serviceId)
|
|
2373
|
+
return false;
|
|
2374
|
+
if (opts.model && s.model !== opts.model)
|
|
2375
|
+
return false;
|
|
2376
|
+
if (opts.routeId && s.routeId !== opts.routeId)
|
|
2377
|
+
return false;
|
|
2378
|
+
if (keyword) {
|
|
2379
|
+
const title = (s.title || '').toLowerCase();
|
|
2380
|
+
const id = (s.id || '').toLowerCase();
|
|
2381
|
+
if (!title.includes(keyword) && !id.includes(keyword))
|
|
2382
|
+
return false;
|
|
2972
2383
|
}
|
|
2973
|
-
return
|
|
2384
|
+
return true;
|
|
2974
2385
|
});
|
|
2975
2386
|
}
|
|
2976
2387
|
/**
|
|
2977
|
-
*
|
|
2388
|
+
* 按 refs 回填日志正文。按 filename 分组,每个分片只 loadLogShard 一次,
|
|
2389
|
+
* 处理完即释放分片引用,避免大量分片正文同时驻留内存。
|
|
2390
|
+
* 供 getLogsBySessionId / searchLogs / getClientClosedLogs 复用。
|
|
2978
2391
|
*/
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
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;
|
|
2392
|
+
/** 按会话取日志(字节偏移随机读,since 下推到索引层)。委托 LogStore。 */
|
|
2393
|
+
getLogsBySessionId(sessionId_1) {
|
|
2394
|
+
return __awaiter(this, arguments, void 0, function* (sessionId, limit = 100, since) {
|
|
2395
|
+
return this.requireLogStore().getBySession('global', sessionId, { limit, since });
|
|
2396
|
+
});
|
|
3014
2397
|
}
|
|
3015
2398
|
/**
|
|
3016
|
-
*
|
|
3017
|
-
*
|
|
3018
|
-
* Claude Code: body.metadata.user_id(兼容新旧格式)
|
|
2399
|
+
* 批量回填多个会话的近期日志(跨会话合并文件读取),委托 LogStore。
|
|
2400
|
+
* 用于 Agent Map 启动重建。
|
|
3019
2401
|
*/
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
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;
|
|
2402
|
+
getRecentLogsBySessions(sessionIds_1) {
|
|
2403
|
+
return __awaiter(this, arguments, void 0, function* (sessionIds, opts = {}) {
|
|
2404
|
+
return this.requireLogStore().getBySessionsBatch('global', sessionIds, opts);
|
|
2405
|
+
});
|
|
3048
2406
|
}
|
|
2407
|
+
// sessionId 提取已移至 LogStore 内部(extractSessionId),fs-database 不再维护会话日志索引
|
|
3049
2408
|
deleteSession(sessionId) {
|
|
3050
2409
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3051
2410
|
const index = this.sessions.findIndex(s => s.id === sessionId);
|
|
3052
2411
|
if (index === -1)
|
|
3053
2412
|
return false;
|
|
3054
2413
|
this.sessions.splice(index, 1);
|
|
3055
|
-
this.sessionLogIndex.delete(sessionId);
|
|
3056
|
-
this.scheduleSessionLogIndexFlush();
|
|
3057
2414
|
yield this.saveSessions();
|
|
2415
|
+
// 关联日志删除走 LogStore tombstone
|
|
2416
|
+
try {
|
|
2417
|
+
yield this.requireLogStore().deleteLogsBySession('global', sessionId);
|
|
2418
|
+
}
|
|
2419
|
+
catch (err) {
|
|
2420
|
+
console.error('[Database] deleteLogsBySession failed:', err);
|
|
2421
|
+
}
|
|
3058
2422
|
return true;
|
|
3059
2423
|
});
|
|
3060
2424
|
}
|
|
3061
2425
|
clearSessions() {
|
|
3062
2426
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3063
2427
|
this.sessions = [];
|
|
3064
|
-
this.sessionLogIndex.clear();
|
|
3065
|
-
this.scheduleSessionLogIndexFlush();
|
|
3066
2428
|
yield this.saveSessions();
|
|
3067
2429
|
});
|
|
3068
2430
|
}
|
|
@@ -3089,140 +2451,18 @@ class FileSystemDatabaseManager {
|
|
|
3089
2451
|
});
|
|
3090
2452
|
}
|
|
3091
2453
|
/**
|
|
3092
|
-
*
|
|
3093
|
-
*
|
|
3094
|
-
* 与 addLog 共享分片写入锁(shardWriteLocks),避免并发写入竞争。
|
|
2454
|
+
* 删除指定会话集合关联的所有日志条目(tombstone,追加写无需重写文件)。
|
|
2455
|
+
* 委托 LogStore。
|
|
3095
2456
|
*/
|
|
3096
2457
|
deleteLogsBySessionIds(sessionIds) {
|
|
3097
2458
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3098
2459
|
if (sessionIds.size === 0)
|
|
3099
2460
|
return { logsDeleted: 0 };
|
|
3100
|
-
|
|
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
|
-
}
|
|
2461
|
+
const ls = this.requireLogStore();
|
|
3115
2462
|
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
2463
|
for (const sid of sessionIds) {
|
|
3222
|
-
|
|
2464
|
+
logsDeleted += yield ls.deleteLogsBySession('global', sid);
|
|
3223
2465
|
}
|
|
3224
|
-
yield this.saveLogsIndex();
|
|
3225
|
-
yield this.saveSessionLogIndexNow();
|
|
3226
2466
|
this.logsCountCache = null;
|
|
3227
2467
|
return { logsDeleted };
|
|
3228
2468
|
});
|
|
@@ -3240,6 +2480,8 @@ class FileSystemDatabaseManager {
|
|
|
3240
2480
|
existing.lastRequestAt = now;
|
|
3241
2481
|
existing.requestCount++;
|
|
3242
2482
|
existing.totalTokens += session.totalTokens || 0;
|
|
2483
|
+
existing.inputTokens = (existing.inputTokens || 0) + (session.inputTokens || 0);
|
|
2484
|
+
existing.outputTokens = (existing.outputTokens || 0) + (session.outputTokens || 0);
|
|
3243
2485
|
if (session.vendorId !== undefined)
|
|
3244
2486
|
existing.vendorId = session.vendorId;
|
|
3245
2487
|
if (session.vendorName !== undefined)
|
|
@@ -3272,6 +2514,8 @@ class FileSystemDatabaseManager {
|
|
|
3272
2514
|
lastRequestAt: now,
|
|
3273
2515
|
requestCount: 1,
|
|
3274
2516
|
totalTokens: session.totalTokens || 0,
|
|
2517
|
+
inputTokens: session.inputTokens || 0,
|
|
2518
|
+
outputTokens: session.outputTokens || 0,
|
|
3275
2519
|
vendorId: session.vendorId,
|
|
3276
2520
|
vendorName: session.vendorName,
|
|
3277
2521
|
serviceId: session.serviceId,
|
|
@@ -3384,8 +2628,7 @@ class FileSystemDatabaseManager {
|
|
|
3384
2628
|
}
|
|
3385
2629
|
// Close method for compatibility (no-op for filesystem database)
|
|
3386
2630
|
close() {
|
|
3387
|
-
//
|
|
3388
|
-
this.flushSessionLogIndex();
|
|
2631
|
+
// 日志索引落盘由 LogStore.close() 负责(main.ts 在 shutdown 时调用)
|
|
3389
2632
|
}
|
|
3390
2633
|
}
|
|
3391
2634
|
exports.FileSystemDatabaseManager = FileSystemDatabaseManager;
|