coding-tool-x 3.6.5 → 3.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.6.5",
3
+ "version": "3.6.6",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -431,6 +431,7 @@ const PATHS = {
431
431
  sessionCache: path.join(CACHE_DIR, 'session-cache.json'),
432
432
  sessionHasCache: path.join(CACHE_DIR, 'session-has-cache.json'),
433
433
  channelModels: path.join(CACHE_DIR, 'channel-models.json'),
434
+ channelBalanceStrategies: path.join(CACHE_DIR, 'channel-balance-strategies.json'),
434
435
  envBackups: path.join(BACKUPS_DIR, 'env'),
435
436
  proxyRuntime: {
436
437
  claude: path.join(RUNTIME_DIR, 'claude-proxy.json'),
@@ -1,7 +1,10 @@
1
1
  const http = require('http');
2
2
  const https = require('https');
3
3
  const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
4
6
  const { URL } = require('url');
7
+ const { PATHS } = require('../../config/paths');
5
8
  const { loadUIConfig } = require('./ui-config');
6
9
 
7
10
  const CACHE_TTL_MS = 5 * 60 * 1000;
@@ -16,6 +19,66 @@ const UNSUPPORTED_BALANCE_PLATFORMS = new Set(['dashscope', 'modelscope']);
16
19
 
17
20
  const balanceCache = new Map();
18
21
  const balanceStrategyCache = new Map();
22
+ let strategyCacheLoaded = false;
23
+
24
+ function getStrategyCacheFilePath() {
25
+ return PATHS.channelBalanceStrategies
26
+ || path.join(PATHS.storage || path.dirname(PATHS.channelModels), 'cache', 'channel-balance-strategies.json');
27
+ }
28
+
29
+ function loadPersistedStrategyCache() {
30
+ if (strategyCacheLoaded) return;
31
+ strategyCacheLoaded = true;
32
+
33
+ try {
34
+ const filePath = getStrategyCacheFilePath();
35
+ if (!fs.existsSync(filePath)) return;
36
+ const payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
+ const entries = payload && typeof payload === 'object' ? payload.entries : null;
38
+ if (!entries || typeof entries !== 'object') return;
39
+ Object.entries(entries).forEach(([key, value]) => {
40
+ const strategy = value?.strategy && typeof value.strategy === 'object' ? value.strategy : null;
41
+ if (strategy?.type) {
42
+ balanceStrategyCache.set(key, strategy);
43
+ }
44
+ });
45
+ } catch {
46
+ // A corrupt strategy cache should only cost a fresh probe, not break balance display.
47
+ }
48
+ }
49
+
50
+ function writePersistedStrategyCache() {
51
+ try {
52
+ const filePath = getStrategyCacheFilePath();
53
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
54
+ const entries = {};
55
+ balanceStrategyCache.forEach((strategy, key) => {
56
+ if (strategy?.type) {
57
+ entries[key] = {
58
+ strategy,
59
+ updatedAt: nowIso()
60
+ };
61
+ }
62
+ });
63
+ fs.writeFileSync(filePath, JSON.stringify({ version: 1, entries }, null, 2), 'utf8');
64
+ } catch {
65
+ // Persisting the optimization is best-effort; in-memory cache still works.
66
+ }
67
+ }
68
+
69
+ function rememberBalanceStrategy(key, strategy) {
70
+ if (!key || !strategy?.type) return;
71
+ loadPersistedStrategyCache();
72
+ balanceStrategyCache.set(key, strategy);
73
+ writePersistedStrategyCache();
74
+ }
75
+
76
+ function forgetBalanceStrategy(key) {
77
+ loadPersistedStrategyCache();
78
+ if (balanceStrategyCache.delete(key)) {
79
+ writePersistedStrategyCache();
80
+ }
81
+ }
19
82
 
20
83
  function nowIso(now = Date.now()) {
21
84
  return new Date(now).toISOString();
@@ -1275,24 +1338,25 @@ async function refreshChannelBalanceSnapshot(source, channel, options = {}) {
1275
1338
 
1276
1339
  let lastSnapshot = null;
1277
1340
  const token = resolveBalanceToken(channel);
1341
+ loadPersistedStrategyCache();
1278
1342
  const cachedStrategy = balanceStrategyCache.get(key);
1279
1343
  if (cachedStrategy) {
1280
1344
  const strategyResult = await tryBalanceStrategy(cachedStrategy, token, channel);
1281
1345
  if (strategyResult?.snapshot?.visible) {
1282
- balanceStrategyCache.set(key, strategyResult.strategy);
1346
+ rememberBalanceStrategy(key, strategyResult.strategy);
1283
1347
  balanceCache.set(key, {
1284
1348
  snapshot: strategyResult.snapshot,
1285
1349
  expiresAt: currentTime + CACHE_TTL_MS
1286
1350
  });
1287
1351
  return strategyResult.snapshot;
1288
1352
  }
1289
- balanceStrategyCache.delete(key);
1353
+ forgetBalanceStrategy(key);
1290
1354
  }
1291
1355
 
1292
1356
  for (const baseUrl of bases) {
1293
1357
  const result = await probeBalanceFromBase(baseUrl, channel, token);
1294
1358
  if (result?.snapshot?.visible) {
1295
- balanceStrategyCache.set(key, result.strategy);
1359
+ rememberBalanceStrategy(key, result.strategy);
1296
1360
  balanceCache.set(key, {
1297
1361
  snapshot: result.snapshot,
1298
1362
  expiresAt: currentTime + CACHE_TTL_MS
@@ -1333,6 +1397,10 @@ function getChannelsForSource(source) {
1333
1397
  return [];
1334
1398
  }
1335
1399
 
1400
+ function getEnabledBalanceChannels(source) {
1401
+ return getChannelsForSource(source).filter(channel => channel?.enabled !== false);
1402
+ }
1403
+
1336
1404
  function isBalanceDisplayEnabled() {
1337
1405
  try {
1338
1406
  return loadUIConfig().channelBalance?.showRemaining === true;
@@ -1369,7 +1437,7 @@ async function getChannelBalances(source, options = {}) {
1369
1437
  return { enabled: false, source: normalizedSource, balances: {} };
1370
1438
  }
1371
1439
 
1372
- const channels = getChannelsForSource(normalizedSource);
1440
+ const channels = getEnabledBalanceChannels(normalizedSource);
1373
1441
  const entries = await mapWithConcurrency(channels, 4, async (channel) => {
1374
1442
  const snapshot = await refreshChannelBalanceSnapshot(normalizedSource, channel, options);
1375
1443
  return [channel.id, snapshot];
@@ -1388,7 +1456,7 @@ async function refreshChannelBalance(source, channelId) {
1388
1456
  return { enabled: false, source: normalizedSource, channelId, balance: makeHiddenSnapshot() };
1389
1457
  }
1390
1458
 
1391
- const channels = getChannelsForSource(normalizedSource);
1459
+ const channels = getEnabledBalanceChannels(normalizedSource);
1392
1460
  const channel = channels.find(item => String(item.id) === String(channelId));
1393
1461
  if (!channel) {
1394
1462
  const error = new Error('Channel not found');
@@ -1408,6 +1476,7 @@ async function refreshChannelBalance(source, channelId) {
1408
1476
  function clearBalanceCache() {
1409
1477
  balanceCache.clear();
1410
1478
  balanceStrategyCache.clear();
1479
+ strategyCacheLoaded = false;
1411
1480
  }
1412
1481
 
1413
1482
  module.exports = {
@@ -1435,6 +1504,7 @@ module.exports = {
1435
1504
  build88CodeUsageSnapshot,
1436
1505
  build88CodeSubscriptionSnapshot,
1437
1506
  buildBalanceProbeStrategies,
1507
+ getEnabledBalanceChannels,
1438
1508
  runBalanceStrategy,
1439
1509
  refreshChannelBalanceSnapshot,
1440
1510
  makeVisibleSnapshot,