aicodeswitch 4.0.0 → 4.0.2

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.
@@ -10,14 +10,20 @@ exports.verifyToken = verifyToken;
10
10
  exports.authMiddleware = authMiddleware;
11
11
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
12
12
  const crypto_1 = __importDefault(require("crypto"));
13
- const AUTH_CODE = process.env.AUTH || '';
14
- const JWT_SECRET = process.env.JWT_SECRET || (AUTH_CODE ? crypto_1.default.createHash('sha256').update(AUTH_CODE).digest('hex') : '');
13
+ // 延迟读取 process.env.AUTH,避免模块加载时 dotenv 尚未执行导致值始终为空
14
+ function getAuthCode() {
15
+ return process.env.AUTH || '';
16
+ }
17
+ function getJwtSecret() {
18
+ const authCode = getAuthCode();
19
+ return process.env.JWT_SECRET || (authCode ? crypto_1.default.createHash('sha256').update(authCode).digest('hex') : '');
20
+ }
15
21
  const TOKEN_EXPIRY = '7d'; // 7天有效期
16
22
  /**
17
23
  * 检查是否启用鉴权
18
24
  */
19
25
  function isAuthEnabled() {
20
- return AUTH_CODE.trim().length > 0;
26
+ return getAuthCode().trim().length > 0;
21
27
  }
22
28
  /**
23
29
  * 验证鉴权码
@@ -26,7 +32,7 @@ function verifyAuthCode(authCode) {
26
32
  if (!isAuthEnabled()) {
27
33
  return true; // 未启用鉴权,直接通过
28
34
  }
29
- return authCode === AUTH_CODE;
35
+ return authCode === getAuthCode();
30
36
  }
31
37
  /**
32
38
  * 生成 JWT Token
@@ -35,14 +41,14 @@ function generateToken() {
35
41
  const payload = {
36
42
  authenticated: true,
37
43
  };
38
- return jsonwebtoken_1.default.sign(payload, JWT_SECRET, { expiresIn: TOKEN_EXPIRY });
44
+ return jsonwebtoken_1.default.sign(payload, getJwtSecret(), { expiresIn: TOKEN_EXPIRY });
39
45
  }
40
46
  /**
41
47
  * 验证 JWT Token
42
48
  */
43
49
  function verifyToken(token) {
44
50
  try {
45
- jsonwebtoken_1.default.verify(token, JWT_SECRET);
51
+ jsonwebtoken_1.default.verify(token, getJwtSecret());
46
52
  return true;
47
53
  }
48
54
  catch (error) {
@@ -56,6 +56,7 @@ class FileSystemDatabaseManager {
56
56
  get sessionsFile() { return path_1.default.join(this.dataPath, 'sessions.json'); }
57
57
  get logsDir() { return path_1.default.join(this.dataPath, 'logs'); }
58
58
  get logsIndexFile() { return path_1.default.join(this.dataPath, 'logs-index.json'); }
59
+ get sessionLogIndexFile() { return path_1.default.join(this.dataPath, 'session-log-index.json'); }
59
60
  get errorLogsFile() { return path_1.default.join(this.dataPath, 'error-logs.json'); }
60
61
  get blacklistFile() { return path_1.default.join(this.dataPath, 'blacklist.json'); }
61
62
  get statisticsFile() { return path_1.default.join(this.dataPath, 'statistics.json'); }
@@ -133,6 +134,42 @@ class FileSystemDatabaseManager {
133
134
  writable: true,
134
135
  value: []
135
136
  });
137
+ Object.defineProperty(this, "sessionLogIndex", {
138
+ enumerable: true,
139
+ configurable: true,
140
+ writable: true,
141
+ value: new Map()
142
+ });
143
+ Object.defineProperty(this, "sessionLogIndexDirty", {
144
+ enumerable: true,
145
+ configurable: true,
146
+ writable: true,
147
+ value: false
148
+ });
149
+ Object.defineProperty(this, "sessionLogIndexDirtyCount", {
150
+ enumerable: true,
151
+ configurable: true,
152
+ writable: true,
153
+ value: 0
154
+ });
155
+ Object.defineProperty(this, "sessionLogIndexFlushTimer", {
156
+ enumerable: true,
157
+ configurable: true,
158
+ writable: true,
159
+ value: null
160
+ });
161
+ Object.defineProperty(this, "SESSION_LOG_INDEX_FLUSH_DELAY", {
162
+ enumerable: true,
163
+ configurable: true,
164
+ writable: true,
165
+ value: 3000
166
+ });
167
+ Object.defineProperty(this, "SESSION_LOG_INDEX_FLUSH_THRESHOLD", {
168
+ enumerable: true,
169
+ configurable: true,
170
+ writable: true,
171
+ value: 50
172
+ });
136
173
  Object.defineProperty(this, "errorLogs", {
137
174
  enumerable: true,
138
175
  configurable: true,
@@ -242,6 +279,8 @@ class FileSystemDatabaseManager {
242
279
  this.loadStatistics(),
243
280
  this.loadMCPs(),
244
281
  ]);
282
+ // 会话日志索引依赖 logShardsIndex,必须在 loadLogsIndex 之后
283
+ yield this.loadSessionLogIndex();
245
284
  });
246
285
  }
247
286
  loadVendors() {
@@ -571,6 +610,96 @@ class FileSystemDatabaseManager {
571
610
  yield promises_1.default.writeFile(this.logsIndexFile, JSON.stringify(this.logShardsIndex, null, 2));
572
611
  });
573
612
  }
613
+ /**
614
+ * 加载会话日志索引,若不存在则从现有日志全量构建
615
+ */
616
+ loadSessionLogIndex() {
617
+ return __awaiter(this, void 0, void 0, function* () {
618
+ try {
619
+ const data = yield promises_1.default.readFile(this.sessionLogIndexFile, 'utf-8');
620
+ const parsed = JSON.parse(data);
621
+ this.sessionLogIndex = new Map(Object.entries(parsed));
622
+ console.log(`[Database] Session log index loaded: ${this.sessionLogIndex.size} sessions`);
623
+ }
624
+ catch (_a) {
625
+ // 索引文件不存在,从现有日志全量构建
626
+ console.log('[Database] Session log index not found, building from existing logs...');
627
+ yield this.buildSessionLogIndex();
628
+ }
629
+ });
630
+ }
631
+ /**
632
+ * 从所有现有日志分片全量构建会话日志索引(首次启动或迁移用)
633
+ */
634
+ buildSessionLogIndex() {
635
+ return __awaiter(this, void 0, void 0, function* () {
636
+ this.sessionLogIndex.clear();
637
+ for (const shard of this.logShardsIndex) {
638
+ const shardLogs = yield this.loadLogShard(shard.filename);
639
+ for (let i = 0; i < shardLogs.length; i++) {
640
+ const sessionId = this.extractSessionIdFromLog(shardLogs[i]);
641
+ if (sessionId) {
642
+ let refs = this.sessionLogIndex.get(sessionId);
643
+ if (!refs) {
644
+ refs = [];
645
+ this.sessionLogIndex.set(sessionId, refs);
646
+ }
647
+ refs.push({ filename: shard.filename, index: i, timestamp: shardLogs[i].timestamp });
648
+ }
649
+ }
650
+ }
651
+ if (this.sessionLogIndex.size > 0) {
652
+ yield this.saveSessionLogIndexNow();
653
+ console.log(`[Database] Session log index built: ${this.sessionLogIndex.size} sessions indexed`);
654
+ }
655
+ });
656
+ }
657
+ /**
658
+ * 将会话日志索引写入磁盘
659
+ */
660
+ saveSessionLogIndexNow() {
661
+ return __awaiter(this, void 0, void 0, function* () {
662
+ const obj = {};
663
+ for (const [key, refs] of this.sessionLogIndex) {
664
+ obj[key] = refs;
665
+ }
666
+ yield promises_1.default.writeFile(this.sessionLogIndexFile, JSON.stringify(obj));
667
+ });
668
+ }
669
+ /**
670
+ * 标记索引脏数据,触发防抖写盘
671
+ */
672
+ scheduleSessionLogIndexFlush() {
673
+ this.sessionLogIndexDirty = true;
674
+ this.sessionLogIndexDirtyCount++;
675
+ // 达到阈值立即刷盘
676
+ if (this.sessionLogIndexDirtyCount >= this.SESSION_LOG_INDEX_FLUSH_THRESHOLD) {
677
+ this.flushSessionLogIndex();
678
+ return;
679
+ }
680
+ // 防抖定时器
681
+ if (!this.sessionLogIndexFlushTimer) {
682
+ this.sessionLogIndexFlushTimer = setTimeout(() => {
683
+ this.flushSessionLogIndex();
684
+ }, this.SESSION_LOG_INDEX_FLUSH_DELAY);
685
+ }
686
+ }
687
+ /**
688
+ * 立即将索引刷盘(关闭时调用)
689
+ */
690
+ flushSessionLogIndex() {
691
+ if (this.sessionLogIndexFlushTimer) {
692
+ clearTimeout(this.sessionLogIndexFlushTimer);
693
+ this.sessionLogIndexFlushTimer = null;
694
+ }
695
+ if (this.sessionLogIndexDirty) {
696
+ this.sessionLogIndexDirty = false;
697
+ this.sessionLogIndexDirtyCount = 0;
698
+ this.saveSessionLogIndexNow().catch(err => {
699
+ console.error('[Database] Failed to flush session log index:', err);
700
+ });
701
+ }
702
+ }
574
703
  /**
575
704
  * 迁移旧的 logs.json 文件到新的分片格式
576
705
  */
@@ -683,6 +812,18 @@ class FileSystemDatabaseManager {
683
812
  this.logShardsIndex = this.logShardsIndex.filter(s => !toDelete.includes(s.filename));
684
813
  if (toDelete.length > 0) {
685
814
  yield this.saveLogsIndex();
815
+ // 同步清理会话日志索引中被删除分片的条目
816
+ const deleteSet = new Set(toDelete);
817
+ for (const [sid, refs] of this.sessionLogIndex) {
818
+ const remaining = refs.filter(r => !deleteSet.has(r.filename));
819
+ if (remaining.length === 0) {
820
+ this.sessionLogIndex.delete(sid);
821
+ }
822
+ else if (remaining.length < refs.length) {
823
+ this.sessionLogIndex.set(sid, remaining);
824
+ }
825
+ }
826
+ yield this.saveSessionLogIndexNow();
686
827
  }
687
828
  });
688
829
  }
@@ -1413,6 +1554,17 @@ class FileSystemDatabaseManager {
1413
1554
  yield this.updateStatistics(logWithId);
1414
1555
  // 清除计数缓存
1415
1556
  this.logsCountCache = null;
1557
+ // 更新会话日志索引
1558
+ const sessionId = this.extractSessionIdFromLog(logWithId);
1559
+ if (sessionId) {
1560
+ let refs = this.sessionLogIndex.get(sessionId);
1561
+ if (!refs) {
1562
+ refs = [];
1563
+ this.sessionLogIndex.set(sessionId, refs);
1564
+ }
1565
+ refs.push({ filename, index: shardLogs.length - 1, timestamp: logWithId.timestamp });
1566
+ this.scheduleSessionLogIndexFlush();
1567
+ }
1416
1568
  });
1417
1569
  }
1418
1570
  getLogs() {
@@ -2381,8 +2533,37 @@ class FileSystemDatabaseManager {
2381
2533
  }
2382
2534
  getLogsBySessionId(sessionId_1) {
2383
2535
  return __awaiter(this, arguments, void 0, function* (sessionId, limit = 100) {
2536
+ const refs = this.sessionLogIndex.get(sessionId);
2537
+ // 有索引:仅加载相关分片,按 index 直接取值
2538
+ if (refs && refs.length > 0) {
2539
+ // 按 filename 分组,避免重复加载同一分片
2540
+ const shardMap = new Map();
2541
+ for (const ref of refs) {
2542
+ let indices = shardMap.get(ref.filename);
2543
+ if (!indices) {
2544
+ indices = [];
2545
+ shardMap.set(ref.filename, indices);
2546
+ }
2547
+ indices.push(ref.index);
2548
+ }
2549
+ const logs = [];
2550
+ for (const [filename, indices] of shardMap) {
2551
+ try {
2552
+ const shardLogs = yield this.loadLogShard(filename);
2553
+ for (const idx of indices) {
2554
+ if (idx >= 0 && idx < shardLogs.length) {
2555
+ logs.push(shardLogs[idx]);
2556
+ }
2557
+ }
2558
+ }
2559
+ catch (_a) {
2560
+ // 分片文件可能已被清理,跳过
2561
+ }
2562
+ }
2563
+ return logs.sort((a, b) => b.timestamp - a.timestamp).slice(0, limit);
2564
+ }
2565
+ // 无索引(兼容旧数据):回退到全扫描
2384
2566
  const allLogs = [];
2385
- // 遍历所有分片
2386
2567
  for (const shard of this.logShardsIndex) {
2387
2568
  const shardLogs = yield this.loadLogShard(shard.filename);
2388
2569
  const filtered = shardLogs.filter(log => this.isLogBelongsToSession(log, sessionId));
@@ -2405,22 +2586,73 @@ class FileSystemDatabaseManager {
2405
2586
  try {
2406
2587
  // body 可能是对象(已解析)或字符串(未解析)
2407
2588
  const body = typeof log.body === 'string' ? JSON.parse(log.body) : log.body;
2408
- if (((_b = body.metadata) === null || _b === void 0 ? void 0 : _b.user_id) === sessionId) {
2409
- return true;
2589
+ if ((_b = body.metadata) === null || _b === void 0 ? void 0 : _b.user_id) {
2590
+ const userId = body.metadata.user_id;
2591
+ // 兼容新旧格式:新版本为 JSON 字符串,旧版本为纯字符串
2592
+ let extractedSessionId = null;
2593
+ try {
2594
+ const parsed = JSON.parse(userId);
2595
+ if (parsed && typeof parsed === 'object' && parsed.session_id) {
2596
+ extractedSessionId = parsed.session_id;
2597
+ }
2598
+ }
2599
+ catch (_c) {
2600
+ // 不是 JSON,按旧版本纯字符串处理
2601
+ extractedSessionId = userId;
2602
+ }
2603
+ if (extractedSessionId === sessionId) {
2604
+ return true;
2605
+ }
2410
2606
  }
2411
2607
  }
2412
- catch (_c) {
2608
+ catch (_d) {
2413
2609
  // 忽略解析错误
2414
2610
  }
2415
2611
  }
2416
2612
  return false;
2417
2613
  }
2614
+ /**
2615
+ * 从日志条目中提取 sessionId(用于索引)
2616
+ * Codex: headers['session_id']
2617
+ * Claude Code: body.metadata.user_id(兼容新旧格式)
2618
+ */
2619
+ extractSessionIdFromLog(log) {
2620
+ var _a, _b;
2621
+ // Codex: headers 中的 session_id
2622
+ const headerSessionId = (_a = log.headers) === null || _a === void 0 ? void 0 : _a['session_id'];
2623
+ if (typeof headerSessionId === 'string')
2624
+ return headerSessionId;
2625
+ // Claude Code: body 中的 metadata.user_id
2626
+ if (log.body) {
2627
+ try {
2628
+ const body = typeof log.body === 'string' ? JSON.parse(log.body) : log.body;
2629
+ if ((_b = body.metadata) === null || _b === void 0 ? void 0 : _b.user_id) {
2630
+ const userId = body.metadata.user_id;
2631
+ try {
2632
+ const parsed = JSON.parse(userId);
2633
+ if (parsed && typeof parsed === 'object' && parsed.session_id) {
2634
+ return parsed.session_id;
2635
+ }
2636
+ }
2637
+ catch (_c) {
2638
+ return userId;
2639
+ }
2640
+ }
2641
+ }
2642
+ catch (_d) {
2643
+ // 忽略解析错误
2644
+ }
2645
+ }
2646
+ return null;
2647
+ }
2418
2648
  deleteSession(sessionId) {
2419
2649
  return __awaiter(this, void 0, void 0, function* () {
2420
2650
  const index = this.sessions.findIndex(s => s.id === sessionId);
2421
2651
  if (index === -1)
2422
2652
  return false;
2423
2653
  this.sessions.splice(index, 1);
2654
+ this.sessionLogIndex.delete(sessionId);
2655
+ this.scheduleSessionLogIndexFlush();
2424
2656
  yield this.saveSessions();
2425
2657
  return true;
2426
2658
  });
@@ -2428,6 +2660,8 @@ class FileSystemDatabaseManager {
2428
2660
  clearSessions() {
2429
2661
  return __awaiter(this, void 0, void 0, function* () {
2430
2662
  this.sessions = [];
2663
+ this.sessionLogIndex.clear();
2664
+ this.scheduleSessionLogIndexFlush();
2431
2665
  yield this.saveSessions();
2432
2666
  });
2433
2667
  }
@@ -2542,8 +2776,8 @@ class FileSystemDatabaseManager {
2542
2776
  }
2543
2777
  // Close method for compatibility (no-op for filesystem database)
2544
2778
  close() {
2545
- // 文件系统数据库不需要关闭连接
2546
- // 所有数据已经持久化到文件
2779
+ // 刷盘会话日志索引
2780
+ this.flushSessionLogIndex();
2547
2781
  }
2548
2782
  }
2549
2783
  exports.FileSystemDatabaseManager = FileSystemDatabaseManager;
@@ -1082,6 +1082,32 @@ const registerRoutes = (dbManager, proxyServer) => {
1082
1082
  res.status(500).json({ error: 'Failed to clear blacklist' });
1083
1083
  }
1084
1084
  })));
1085
+ // 获取所有规则的当前状态
1086
+ app.get('/api/rules/status', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
1087
+ // 获取所有规则
1088
+ const allRules = dbManager.getRules();
1089
+ // 获取有状态记录的规则
1090
+ const statusMap = rules_status_service_1.rulesStatusBroadcaster.getAllRuleStatuses();
1091
+ // 将数组转换为 Map 以便快速查找
1092
+ const statusMapByRuleId = new Map(statusMap.map(status => [status.ruleId, status]));
1093
+ // 合并所有规则的状态
1094
+ const allStatuses = allRules.map(rule => {
1095
+ const existingStatus = statusMapByRuleId.get(rule.id);
1096
+ if (existingStatus) {
1097
+ // 如果有状态记录,返回记录的状态
1098
+ return existingStatus;
1099
+ }
1100
+ else {
1101
+ // 如果没有状态记录,返回默认的 idle 状态
1102
+ return {
1103
+ ruleId: rule.id,
1104
+ status: 'idle',
1105
+ timestamp: Date.now(),
1106
+ };
1107
+ }
1108
+ });
1109
+ res.json(allStatuses);
1110
+ })));
1085
1111
  // 清除规则的错误状态(广播 idle 状态给所有客户端)
1086
1112
  app.post('/api/rules/:id/clear-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1087
1113
  const { id } = req.params;
@@ -1100,7 +1126,7 @@ const registerRoutes = (dbManager, proxyServer) => {
1100
1126
  res.status(404).json({ error: 'Route not found' });
1101
1127
  return;
1102
1128
  }
1103
- // 广播 idle 状态给所有客户端
1129
+ // 标记规则为 idle 状态
1104
1130
  rules_status_service_1.rulesStatusBroadcaster.markRuleIdle(route.id, id);
1105
1131
  res.json({ success: true });
1106
1132
  })));
@@ -1946,32 +1972,24 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1946
1972
  });
1947
1973
  // 创建 WebSocket 服务器用于工具安装
1948
1974
  const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
1949
- // 创建 WebSocket 服务器用于规则状态
1950
- const rulesStatusWss = (0, rules_status_service_1.createRulesStatusWSServer)();
1951
1975
  // 设置黑名单检查函数,用于在规则状态同步时检查黑名单是否已过期
1952
1976
  rules_status_service_1.rulesStatusBroadcaster.setBlacklistChecker((serviceId, routeId, contentType) => __awaiter(void 0, void 0, void 0, function* () {
1953
1977
  // 检查服务��否在黑名单中
1954
1978
  const isBlacklisted = yield dbManager.isServiceBlacklisted(serviceId, routeId, contentType);
1955
1979
  return isBlacklisted;
1956
1980
  }));
1957
- // 将 WebSocket 服务器附加到 HTTP 服务器
1981
+ // 将 WebSocket 服务器附加到 HTTP 服务器(仅用于工具安装)
1958
1982
  server.on('upgrade', (request, socket, head) => {
1959
1983
  if (request.url === '/api/tools/install') {
1960
1984
  toolInstallWss.handleUpgrade(request, socket, head, (ws) => {
1961
1985
  toolInstallWss.emit('connection', ws, request);
1962
1986
  });
1963
1987
  }
1964
- else if (request.url === '/api/rules/status') {
1965
- rulesStatusWss.handleUpgrade(request, socket, head, (ws) => {
1966
- rulesStatusWss.emit('connection', ws, request);
1967
- });
1968
- }
1969
1988
  else {
1970
1989
  socket.destroy();
1971
1990
  }
1972
1991
  });
1973
1992
  console.log(`WebSocket server for tool installation attached to ws://${host}:${port}/api/tools/install`);
1974
- console.log(`WebSocket server for rules status attached to ws://${host}:${port}/api/rules/status`);
1975
1993
  let isShuttingDown = false;
1976
1994
  let shutdownPromise = null;
1977
1995
  const shutdown = (signal) => __awaiter(void 0, void 0, void 0, function* () {