coding-tool-x 3.7.6 → 3.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/assets/{Analytics-wCRLv26m.js → Analytics-BE5H9JGG.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-aa1blEHh.js → ConfigTemplates-z118k1BE.js} +1 -1
- package/dist/web/assets/{Home-eIxO50-k.js → Home-CBhtmOIN.js} +1 -1
- package/dist/web/assets/{PluginManager-BAwGFNjN.js → PluginManager-CGhfZ3t8.js} +1 -1
- package/dist/web/assets/ProjectList-BMPLJtgQ.js +1 -0
- package/dist/web/assets/ProjectList-VeMNXfgO.css +1 -0
- package/dist/web/assets/SessionList-C4chCPxK.css +1 -0
- package/dist/web/assets/SessionList-D9e7wGF1.js +1 -0
- package/dist/web/assets/{SkillManager-Bhzs1O5S.js → SkillManager-DJpdUmnk.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CLdX-pTT.js → WorkspaceManager-C0Ud_V7v.js} +1 -1
- package/dist/web/assets/index-CeTaG4IX.js +2 -0
- package/dist/web/assets/index-S8wrU2Nf.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/commands/daemon.js +3 -1
- package/src/commands/port-config.js +0 -2
- package/src/commands/switch.js +1 -5
- package/src/commands/ui.js +4 -3
- package/src/commands/workspace.js +35 -3
- package/src/config/loader.js +76 -5
- package/src/index.js +11 -0
- package/src/server/api/config.js +0 -3
- package/src/server/api/omp-channels.js +29 -9
- package/src/server/api/omp-config.js +6 -8
- package/src/server/api/settings.js +0 -2
- package/src/server/api/workspaces.js +39 -1
- package/src/server/services/dashboard-snapshot-worker.js +1 -0
- package/src/server/services/https-cert.js +3 -2
- package/src/server/services/model-detector.js +106 -26
- package/src/server/services/omp-auth-providers.js +1 -0
- package/src/server/services/omp-config.js +22 -0
- package/src/server/services/omp-settings-manager.js +31 -16
- package/src/server/services/session-snapshots.js +1 -0
- package/src/server/services/sessions.js +35 -19
- package/src/server/services/web-build.js +2 -1
- package/src/server/services/workspace-service.js +204 -98
- package/src/utils/cli-flags.js +7 -0
- package/src/utils/session.js +10 -4
- package/dist/web/assets/ProjectList-CRiAEixW.js +0 -1
- package/dist/web/assets/ProjectList-Dw2omgOH.css +0 -1
- package/dist/web/assets/SessionList-BQ0FW6Gd.js +0 -1
- package/dist/web/assets/SessionList-kYJ6-Ies.css +0 -1
- package/dist/web/assets/index-B_nFqixU.css +0 -1
- package/dist/web/assets/index-ClMNA9Hf.js +0 -2
|
@@ -233,6 +233,9 @@ const MODEL_ALIASES = {
|
|
|
233
233
|
};
|
|
234
234
|
|
|
235
235
|
const TEST_TIMEOUT_MS = 10000; // 10 seconds per model test
|
|
236
|
+
const MODEL_LIST_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
237
|
+
const MODEL_LIST_FAILURE_TTL_MS = 5 * 60 * 1000;
|
|
238
|
+
const MODEL_LIST_AUTH_FAILURE_TTL_MS = 60 * 60 * 1000;
|
|
236
239
|
const CLAUDE_CODE_BETA_HEADER = 'claude-code-20250219,interleaved-thinking-2025-05-14';
|
|
237
240
|
|
|
238
241
|
const MODEL_UNAVAILABLE_HINTS = [
|
|
@@ -797,6 +800,84 @@ function isSignatureCacheValid(cacheEntry, signatureKey, expectedSignature) {
|
|
|
797
800
|
return cacheEntry[signatureKey] === expectedSignature;
|
|
798
801
|
}
|
|
799
802
|
|
|
803
|
+
function getCacheAgeMs(timestamp, now = Date.now()) {
|
|
804
|
+
const checkedAt = Date.parse(timestamp || '');
|
|
805
|
+
return Number.isFinite(checkedAt) ? Math.max(0, now - checkedAt) : Infinity;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function isCacheFresh(timestamp, ttlMs, now = Date.now()) {
|
|
809
|
+
return getCacheAgeMs(timestamp, now) < ttlMs;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function getModelListFailureTtl(statusCode) {
|
|
813
|
+
return [401, 403, 404, 429].includes(Number(statusCode))
|
|
814
|
+
? MODEL_LIST_AUTH_FAILURE_TTL_MS
|
|
815
|
+
: MODEL_LIST_FAILURE_TTL_MS;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function buildCachedModelListResult(entry, listSignature, forceRefresh) {
|
|
819
|
+
if (forceRefresh || !isSignatureCacheValid(entry, 'listSignature', listSignature)) {
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const failure = entry.modelListFailure;
|
|
824
|
+
if (failure && Date.parse(failure.retryAfter || '') > Date.now()) {
|
|
825
|
+
return {
|
|
826
|
+
models: Array.isArray(entry.fetchedModels) ? entry.fetchedModels : [],
|
|
827
|
+
supported: true,
|
|
828
|
+
cached: true,
|
|
829
|
+
stale: true,
|
|
830
|
+
backoff: true,
|
|
831
|
+
fallbackUsed: false,
|
|
832
|
+
error: failure.error || '模型列表暂时不可用',
|
|
833
|
+
errorHint: failure.errorHint || '请稍后重试,或手动填写模型名称',
|
|
834
|
+
statusCode: failure.statusCode,
|
|
835
|
+
lastChecked: entry.lastChecked || failure.failedAt || null,
|
|
836
|
+
retryAfter: failure.retryAfter
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (!Array.isArray(entry.fetchedModels)) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const stale = !isCacheFresh(entry.lastChecked, MODEL_LIST_CACHE_TTL_MS);
|
|
845
|
+
const result = {
|
|
846
|
+
models: entry.fetchedModels,
|
|
847
|
+
supported: true,
|
|
848
|
+
cached: true,
|
|
849
|
+
fallbackUsed: false,
|
|
850
|
+
error: null,
|
|
851
|
+
lastChecked: entry.lastChecked || null
|
|
852
|
+
};
|
|
853
|
+
if (stale) {
|
|
854
|
+
result.stale = true;
|
|
855
|
+
result.errorHint = '模型目录缓存已过期;可点击“刷新可选模型”更新。';
|
|
856
|
+
}
|
|
857
|
+
return result;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function saveModelListFailure(cache, cacheKey, listSignature, result = {}) {
|
|
861
|
+
const now = new Date();
|
|
862
|
+
const ttlMs = getModelListFailureTtl(result.statusCode);
|
|
863
|
+
const previous = cache[cacheKey] || {};
|
|
864
|
+
const failure = {
|
|
865
|
+
error: result.error || '模型列表暂时不可用',
|
|
866
|
+
errorHint: result.errorHint || '请稍后重试,或手动填写模型名称',
|
|
867
|
+
statusCode: result.statusCode || null,
|
|
868
|
+
failedAt: now.toISOString(),
|
|
869
|
+
retryAfter: new Date(now.getTime() + ttlMs).toISOString()
|
|
870
|
+
};
|
|
871
|
+
cache[cacheKey] = {
|
|
872
|
+
...previous,
|
|
873
|
+
fetchedModels: Array.isArray(previous.fetchedModels) ? previous.fetchedModels : [],
|
|
874
|
+
listSignature,
|
|
875
|
+
modelListFailure: failure
|
|
876
|
+
};
|
|
877
|
+
saveModelCache(cache);
|
|
878
|
+
return failure;
|
|
879
|
+
}
|
|
880
|
+
|
|
800
881
|
/**
|
|
801
882
|
* Test if a specific model is available for a channel
|
|
802
883
|
* @param {Object} channel - Channel configuration
|
|
@@ -869,8 +950,10 @@ async function probeModelAvailability(channel, channelType, options = {}) {
|
|
|
869
950
|
});
|
|
870
951
|
const cacheKey = getChannelCacheKey(channel, probeSignature);
|
|
871
952
|
|
|
872
|
-
// Return cached result
|
|
873
|
-
if (!forceRefresh
|
|
953
|
+
// Return cached result only while the matching probe result is still fresh.
|
|
954
|
+
if (!forceRefresh
|
|
955
|
+
&& isSignatureCacheValid(cache[cacheKey], 'probeSignature', probeSignature)
|
|
956
|
+
&& isCacheFresh(cache[cacheKey].lastChecked, MODEL_LIST_CACHE_TTL_MS)) {
|
|
874
957
|
return {
|
|
875
958
|
availableModels: cache[cacheKey].availableModels || [],
|
|
876
959
|
preferredTestModel: cache[cacheKey].preferredTestModel || null,
|
|
@@ -1026,21 +1109,17 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1026
1109
|
});
|
|
1027
1110
|
const cacheKey = getChannelCacheKey(channel, listSignature);
|
|
1028
1111
|
|
|
1029
|
-
//
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
models: cache[cacheKey].fetchedModels || [],
|
|
1035
|
-
supported: true,
|
|
1036
|
-
cached: true,
|
|
1037
|
-
fallbackUsed: false,
|
|
1038
|
-
error: null,
|
|
1039
|
-
lastChecked: cache[cacheKey].lastChecked
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1112
|
+
// Reuse a fresh catalog immediately. Expired catalogs are also returned as
|
|
1113
|
+
// stale data so opening the editor never blocks on the provider; the user
|
|
1114
|
+
// can explicitly request a refresh.
|
|
1115
|
+
const cachedResult = buildCachedModelListResult(cache[cacheKey], listSignature, forceRefresh);
|
|
1116
|
+
if (cachedResult) return cachedResult;
|
|
1042
1117
|
|
|
1043
1118
|
return new Promise((resolve) => {
|
|
1119
|
+
const resolveFailure = (result) => {
|
|
1120
|
+
saveModelListFailure(cache, cacheKey, listSignature, result);
|
|
1121
|
+
resolve(result);
|
|
1122
|
+
};
|
|
1044
1123
|
try {
|
|
1045
1124
|
const baseUrl = channel.baseUrl.trim().replace(/\/+$/, '');
|
|
1046
1125
|
const endpoint = capability.modelListEndpoint; // e.g. '/v1/models'
|
|
@@ -1095,7 +1174,8 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1095
1174
|
availableModels: cache[cacheKey]?.availableModels || [],
|
|
1096
1175
|
preferredTestModel: cache[cacheKey]?.preferredTestModel || null,
|
|
1097
1176
|
probeSignature: cache[cacheKey]?.probeSignature || null,
|
|
1098
|
-
listSignature
|
|
1177
|
+
listSignature,
|
|
1178
|
+
modelListFailure: null
|
|
1099
1179
|
};
|
|
1100
1180
|
|
|
1101
1181
|
cache[cacheKey] = cacheEntry;
|
|
@@ -1113,7 +1193,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1113
1193
|
});
|
|
1114
1194
|
} catch (parseError) {
|
|
1115
1195
|
console.error(`[ModelDetector] Failed to parse models response: ${parseError.message}`);
|
|
1116
|
-
|
|
1196
|
+
resolveFailure({
|
|
1117
1197
|
models: [],
|
|
1118
1198
|
supported: true,
|
|
1119
1199
|
cached: false,
|
|
@@ -1133,7 +1213,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1133
1213
|
errorMessage = 'Cloudflare 防护拦截,无法自动获取模型列表';
|
|
1134
1214
|
errorHint = '该 API 端点受 Cloudflare 保护,请手动填写模型名称';
|
|
1135
1215
|
console.warn(`[ModelDetector] Cloudflare protection detected for ${channel.name}, no fallback models injected`);
|
|
1136
|
-
|
|
1216
|
+
resolveFailure({
|
|
1137
1217
|
models: [],
|
|
1138
1218
|
supported: false,
|
|
1139
1219
|
cached: false,
|
|
@@ -1146,7 +1226,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1146
1226
|
errorMessage = 'API 密钥认证失败';
|
|
1147
1227
|
errorHint = '请检查 API 密钥是否正确配置';
|
|
1148
1228
|
console.error(`[ModelDetector] Authentication failed for ${channel.name}: ${res.statusCode} - ${errorMessage}`);
|
|
1149
|
-
|
|
1229
|
+
resolveFailure({
|
|
1150
1230
|
models: [],
|
|
1151
1231
|
supported: true,
|
|
1152
1232
|
cached: false,
|
|
@@ -1159,7 +1239,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1159
1239
|
errorMessage = '访问被拒绝';
|
|
1160
1240
|
errorHint = '请检查 API 密钥权限或联系服务提供商';
|
|
1161
1241
|
console.error(`[ModelDetector] Access denied for ${channel.name}: ${res.statusCode} - ${errorMessage}`);
|
|
1162
|
-
|
|
1242
|
+
resolveFailure({
|
|
1163
1243
|
models: [],
|
|
1164
1244
|
supported: true,
|
|
1165
1245
|
cached: false,
|
|
@@ -1171,7 +1251,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1171
1251
|
}
|
|
1172
1252
|
} else if (res.statusCode === 404) {
|
|
1173
1253
|
console.warn(`[ModelDetector] Model list endpoint not found for ${channel.name}`);
|
|
1174
|
-
|
|
1254
|
+
resolveFailure({
|
|
1175
1255
|
models: [],
|
|
1176
1256
|
supported: false,
|
|
1177
1257
|
cached: false,
|
|
@@ -1182,7 +1262,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1182
1262
|
});
|
|
1183
1263
|
} else if (res.statusCode === 429) {
|
|
1184
1264
|
console.warn(`[ModelDetector] Rate limited for ${channel.name}`);
|
|
1185
|
-
|
|
1265
|
+
resolveFailure({
|
|
1186
1266
|
models: [],
|
|
1187
1267
|
supported: true,
|
|
1188
1268
|
cached: false,
|
|
@@ -1206,7 +1286,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1206
1286
|
})
|
|
1207
1287
|
.catch((error) => {
|
|
1208
1288
|
console.error(`[ModelDetector] Failed to read models response: ${error.message}`);
|
|
1209
|
-
|
|
1289
|
+
resolveFailure({
|
|
1210
1290
|
models: [],
|
|
1211
1291
|
supported: true,
|
|
1212
1292
|
cached: false,
|
|
@@ -1218,7 +1298,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1218
1298
|
|
|
1219
1299
|
req.on('error', (error) => {
|
|
1220
1300
|
console.error(`[ModelDetector] Network error fetching models from ${channel.name}: ${error.message}`);
|
|
1221
|
-
|
|
1301
|
+
resolveFailure({
|
|
1222
1302
|
models: [],
|
|
1223
1303
|
supported: true,
|
|
1224
1304
|
cached: false,
|
|
@@ -1230,7 +1310,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1230
1310
|
req.on('timeout', () => {
|
|
1231
1311
|
req.destroy();
|
|
1232
1312
|
console.error(`[ModelDetector] Timeout fetching models from ${channel.name}`);
|
|
1233
|
-
|
|
1313
|
+
resolveFailure({
|
|
1234
1314
|
models: [],
|
|
1235
1315
|
supported: true,
|
|
1236
1316
|
cached: false,
|
|
@@ -1243,7 +1323,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1243
1323
|
|
|
1244
1324
|
} catch (error) {
|
|
1245
1325
|
console.error(`[ModelDetector] Error in fetchModelsFromProvider: ${error.message}`);
|
|
1246
|
-
|
|
1326
|
+
resolveFailure({
|
|
1247
1327
|
models: [],
|
|
1248
1328
|
supported: true,
|
|
1249
1329
|
cached: false,
|
|
@@ -229,6 +229,28 @@ function readTextFile(filePath, fallback = '') {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
function getOmpStatus(env = process.env, options = {}) {
|
|
232
|
+
if (options.resolveRuntime === false) {
|
|
233
|
+
const paths = getOmpPaths(env, options);
|
|
234
|
+
return {
|
|
235
|
+
installed: fs.existsSync(paths.agentDir),
|
|
236
|
+
runtime: 'omp',
|
|
237
|
+
command: getOmpCommand(env),
|
|
238
|
+
commandSource: 'not-probed',
|
|
239
|
+
agentDir: paths.agentDir,
|
|
240
|
+
settingsPath: paths.settings,
|
|
241
|
+
authPath: paths.auth,
|
|
242
|
+
modelsPath: paths.modelsYml,
|
|
243
|
+
modelsYmlPath: paths.modelsYml,
|
|
244
|
+
modelsJsonLegacyPath: paths.modelsJsonLegacy,
|
|
245
|
+
sessionsDir: paths.sessions,
|
|
246
|
+
skillsDir: paths.skills,
|
|
247
|
+
promptsDir: paths.prompts,
|
|
248
|
+
commandsDir: paths.commands,
|
|
249
|
+
extensionsDir: paths.extensions,
|
|
250
|
+
themesDir: paths.themes,
|
|
251
|
+
packagesDir: paths.packages
|
|
252
|
+
};
|
|
253
|
+
}
|
|
232
254
|
const runtime = resolveOmpRuntime(env, options);
|
|
233
255
|
const paths = getOmpPaths(env, { ...options, runtime });
|
|
234
256
|
return {
|
|
@@ -20,8 +20,11 @@ const THINKING_MODES = new Set([
|
|
|
20
20
|
|
|
21
21
|
let lastManagedOmpSyncResult = null;
|
|
22
22
|
|
|
23
|
-
function getOmpPaths(
|
|
24
|
-
|
|
23
|
+
function getOmpPaths(env = process.env, options = {}) {
|
|
24
|
+
// Channel CRUD only needs the native OMP file layout. Do not start OMP just
|
|
25
|
+
// to discover a path: OMP_CONFIG_DIR, OMP_PROFILE and OMP_CODING_AGENT_DIR
|
|
26
|
+
// are sufficient to derive it, and a blocked CLI must not block a save.
|
|
27
|
+
return ompConfig.getOmpPaths(env, { ...options, resolveRuntime: false });
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
function ensureOmpDir(...args) {
|
|
@@ -257,7 +260,8 @@ function readOmpCatalogModels(command, providerId, options = {}) {
|
|
|
257
260
|
...process.env,
|
|
258
261
|
...(options.env || {})
|
|
259
262
|
},
|
|
260
|
-
timeout: options.catalogTimeout || 5000
|
|
263
|
+
timeout: options.catalogTimeout || 5000,
|
|
264
|
+
windowsHide: true
|
|
261
265
|
});
|
|
262
266
|
const status = result?.status === undefined || result?.status === null ? 0 : result.status;
|
|
263
267
|
if (result?.error || status !== 0) return [];
|
|
@@ -270,6 +274,9 @@ function readOmpCatalogModels(command, providerId, options = {}) {
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
function buildCatalogIndex(channel = {}, options = {}) {
|
|
277
|
+
if (options.catalogFromCli !== true) {
|
|
278
|
+
return new Map();
|
|
279
|
+
}
|
|
273
280
|
const runtime = options.runtime
|
|
274
281
|
|| (typeof ompConfig.resolveOmpRuntime === 'function'
|
|
275
282
|
? ompConfig.resolveOmpRuntime(options.env || process.env, options.runtimeOptions || {})
|
|
@@ -499,7 +506,8 @@ function runModelsJson(command, env, options = {}) {
|
|
|
499
506
|
...process.env,
|
|
500
507
|
...env
|
|
501
508
|
},
|
|
502
|
-
timeout: options.timeout || 5000
|
|
509
|
+
timeout: options.timeout || 5000,
|
|
510
|
+
windowsHide: true
|
|
503
511
|
});
|
|
504
512
|
if (typeof result === 'string' || Buffer.isBuffer(result)) {
|
|
505
513
|
return { status: 0, stdout: result, stderr: '' };
|
|
@@ -515,7 +523,8 @@ function collectVisibleProviderIds(command, env, options = {}) {
|
|
|
515
523
|
...process.env,
|
|
516
524
|
...env
|
|
517
525
|
},
|
|
518
|
-
timeout: options.visibilityTimeout || options.timeout || 5000
|
|
526
|
+
timeout: options.visibilityTimeout || options.timeout || 5000,
|
|
527
|
+
windowsHide: true
|
|
519
528
|
});
|
|
520
529
|
const status = result?.status === undefined || result?.status === null ? 0 : result.status;
|
|
521
530
|
if (result?.error || status !== 0) return [];
|
|
@@ -658,15 +667,17 @@ function collectManagedVisibility(channels = [], modelsConfig = { providers: {}
|
|
|
658
667
|
}
|
|
659
668
|
});
|
|
660
669
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
.
|
|
669
|
-
|
|
670
|
+
if (options.discoverDisabledProviders === true) {
|
|
671
|
+
const env = options.env || process.env;
|
|
672
|
+
const runtime = options.runtime
|
|
673
|
+
|| (typeof ompConfig.resolveOmpRuntime === 'function'
|
|
674
|
+
? ompConfig.resolveOmpRuntime(env, options.runtimeOptions || {})
|
|
675
|
+
: null);
|
|
676
|
+
if (runtime && runtime.runtime === 'omp' && runtime.installed) {
|
|
677
|
+
collectVisibleProviderIds(runtime.command, env, options)
|
|
678
|
+
.filter(providerId => providerId && !isManagedProviderId(providerId))
|
|
679
|
+
.forEach(providerId => pushUnique(managedDisabledProviders, providerId));
|
|
680
|
+
}
|
|
670
681
|
}
|
|
671
682
|
|
|
672
683
|
return {
|
|
@@ -874,7 +885,9 @@ function writeManagedOmpProviders(channels = [], options = {}) {
|
|
|
874
885
|
const backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
|
|
875
886
|
writeModelsConfig(config, paths.modelsYml);
|
|
876
887
|
const visibility = syncManagedOmpVisibility(channels, config, options);
|
|
877
|
-
const validation =
|
|
888
|
+
const validation = options.validateWithCli === true
|
|
889
|
+
? validateOmpModelsConfig(options)
|
|
890
|
+
: { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
|
|
878
891
|
const warnings = [
|
|
879
892
|
...(visibility.warnings || []),
|
|
880
893
|
...(validation.warnings || [])
|
|
@@ -916,7 +929,9 @@ function removeManagedOmpProviders(options = {}) {
|
|
|
916
929
|
if (before !== after) {
|
|
917
930
|
backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
|
|
918
931
|
writeModelsConfig(config, paths.modelsYml);
|
|
919
|
-
validation =
|
|
932
|
+
validation = options.validateWithCli === true
|
|
933
|
+
? validateOmpModelsConfig(options)
|
|
934
|
+
: { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
|
|
920
935
|
}
|
|
921
936
|
recordManagedOmpSyncResult({
|
|
922
937
|
path: paths.modelsYml,
|
|
@@ -46,6 +46,7 @@ function runSessionSnapshotWorker(source, projectName, config = {}, options = {}
|
|
|
46
46
|
const workerPath = path.join(__dirname, 'session-snapshot-worker.js');
|
|
47
47
|
const child = fork(workerPath, [], {
|
|
48
48
|
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
49
|
+
windowsHide: true,
|
|
49
50
|
env: {
|
|
50
51
|
...process.env,
|
|
51
52
|
CC_TOOL_SESSION_SNAPSHOT_WORKER: '1'
|
|
@@ -17,6 +17,17 @@ const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
|
|
|
17
17
|
const CODEX_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.codex.config), 'projects');
|
|
18
18
|
const GEMINI_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'projects');
|
|
19
19
|
|
|
20
|
+
function resolveProjectsDir(config = {}) {
|
|
21
|
+
return config.projectsDir || CLAUDE_PROJECTS_DIR;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function withResolvedProjectsDir(config = {}) {
|
|
25
|
+
return {
|
|
26
|
+
...config,
|
|
27
|
+
projectsDir: resolveProjectsDir(config)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
// Base directory for cc-tool data
|
|
21
32
|
function getCcToolDir() {
|
|
22
33
|
return PATHS.base;
|
|
@@ -182,7 +193,7 @@ function sliceClaudeSessionContentByUserMessage(content, afterUserMessageNumber)
|
|
|
182
193
|
|
|
183
194
|
// Get all projects with stats (async version)
|
|
184
195
|
async function getProjects(config) {
|
|
185
|
-
const projectsDir = config
|
|
196
|
+
const projectsDir = resolveProjectsDir(config);
|
|
186
197
|
|
|
187
198
|
if (!fs.existsSync(projectsDir)) {
|
|
188
199
|
return [];
|
|
@@ -383,16 +394,17 @@ function extractCwdFromSessionHeader(sessionFile) {
|
|
|
383
394
|
|
|
384
395
|
// Get projects with detailed stats (with caching) - async version
|
|
385
396
|
async function getProjectsWithStats(config, options = {}) {
|
|
397
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
386
398
|
if (!options.force) {
|
|
387
399
|
// Check enhanced cache first
|
|
388
|
-
const cacheKey = `${CacheKeys.PROJECTS}${
|
|
400
|
+
const cacheKey = `${CacheKeys.PROJECTS}${resolvedConfig.projectsDir}`;
|
|
389
401
|
const enhancedCached = globalCache.get(cacheKey);
|
|
390
402
|
if (enhancedCached) {
|
|
391
403
|
return enhancedCached;
|
|
392
404
|
}
|
|
393
405
|
|
|
394
406
|
// Check old cache
|
|
395
|
-
const cached = getCachedProjects(
|
|
407
|
+
const cached = getCachedProjects(resolvedConfig);
|
|
396
408
|
if (cached) {
|
|
397
409
|
globalCache.set(cacheKey, cached, 300000); // 5分钟
|
|
398
410
|
return cached;
|
|
@@ -400,22 +412,22 @@ async function getProjectsWithStats(config, options = {}) {
|
|
|
400
412
|
}
|
|
401
413
|
|
|
402
414
|
try {
|
|
403
|
-
const data = await buildProjectsWithStats(
|
|
415
|
+
const data = await buildProjectsWithStats(resolvedConfig);
|
|
404
416
|
if (!Array.isArray(data)) {
|
|
405
|
-
console.warn(`[getProjectsWithStats] Unexpected non-array result for ${
|
|
417
|
+
console.warn(`[getProjectsWithStats] Unexpected non-array result for ${resolvedConfig.projectsDir}, returning empty array.`);
|
|
406
418
|
return [];
|
|
407
419
|
}
|
|
408
|
-
setCachedProjects(
|
|
409
|
-
globalCache.set(`${CacheKeys.PROJECTS}${
|
|
420
|
+
setCachedProjects(resolvedConfig, data);
|
|
421
|
+
globalCache.set(`${CacheKeys.PROJECTS}${resolvedConfig.projectsDir}`, data, 300000);
|
|
410
422
|
return data;
|
|
411
423
|
} catch (err) {
|
|
412
|
-
console.error(`[getProjectsWithStats] Failed to build projects for ${
|
|
424
|
+
console.error(`[getProjectsWithStats] Failed to build projects for ${resolvedConfig.projectsDir}:`, err);
|
|
413
425
|
return [];
|
|
414
426
|
}
|
|
415
427
|
}
|
|
416
428
|
|
|
417
429
|
async function buildProjectsWithStats(config) {
|
|
418
|
-
const projectsDir = config
|
|
430
|
+
const projectsDir = resolveProjectsDir(config);
|
|
419
431
|
|
|
420
432
|
if (!fs.existsSync(projectsDir)) {
|
|
421
433
|
return [];
|
|
@@ -482,7 +494,7 @@ async function buildProjectsWithStats(config) {
|
|
|
482
494
|
|
|
483
495
|
// 获取 Claude 项目/会话数量(轻量统计)
|
|
484
496
|
function getProjectAndSessionCounts(config) {
|
|
485
|
-
const projectsDir = config
|
|
497
|
+
const projectsDir = resolveProjectsDir(config);
|
|
486
498
|
if (!fs.existsSync(projectsDir)) {
|
|
487
499
|
return { projectCount: 0, sessionCount: 0 };
|
|
488
500
|
}
|
|
@@ -672,7 +684,8 @@ async function getSessionsForProject(config, projectName, options = {}) {
|
|
|
672
684
|
|
|
673
685
|
// Delete a session
|
|
674
686
|
function deleteSession(config, projectName, sessionId) {
|
|
675
|
-
const
|
|
687
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
688
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
676
689
|
const sessionFile = path.join(projectDir, sessionId + '.jsonl');
|
|
677
690
|
|
|
678
691
|
if (!fs.existsSync(sessionFile)) {
|
|
@@ -680,14 +693,15 @@ function deleteSession(config, projectName, sessionId) {
|
|
|
680
693
|
}
|
|
681
694
|
|
|
682
695
|
fs.unlinkSync(sessionFile);
|
|
683
|
-
invalidateProjectsCache(
|
|
696
|
+
invalidateProjectsCache(resolvedConfig);
|
|
684
697
|
invalidateSessionResultCache(projectName);
|
|
685
698
|
return { success: true };
|
|
686
699
|
}
|
|
687
700
|
|
|
688
701
|
// Fork a session
|
|
689
702
|
function forkSession(config, projectName, sessionId, options = {}) {
|
|
690
|
-
const
|
|
703
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
704
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
691
705
|
const sessionFile = path.join(projectDir, sessionId + '.jsonl');
|
|
692
706
|
|
|
693
707
|
if (!fs.existsSync(sessionFile)) {
|
|
@@ -716,7 +730,7 @@ function forkSession(config, projectName, sessionId, options = {}) {
|
|
|
716
730
|
const { setAlias } = require('./alias');
|
|
717
731
|
setAlias(newSessionId, options.alias);
|
|
718
732
|
}
|
|
719
|
-
invalidateProjectsCache(
|
|
733
|
+
invalidateProjectsCache(resolvedConfig);
|
|
720
734
|
invalidateSessionResultCache(projectName);
|
|
721
735
|
|
|
722
736
|
return {
|
|
@@ -768,7 +782,8 @@ function saveSessionOrder(projectName, order) {
|
|
|
768
782
|
|
|
769
783
|
// Delete a project (remove the entire project directory)
|
|
770
784
|
function deleteProject(config, projectName) {
|
|
771
|
-
const
|
|
785
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
786
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
772
787
|
|
|
773
788
|
if (!fs.existsSync(projectDir)) {
|
|
774
789
|
throw new Error('Project not found');
|
|
@@ -778,19 +793,20 @@ function deleteProject(config, projectName) {
|
|
|
778
793
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
779
794
|
|
|
780
795
|
// Remove from order file if exists
|
|
781
|
-
const order = getProjectOrder(
|
|
796
|
+
const order = getProjectOrder(resolvedConfig);
|
|
782
797
|
const newOrder = order.filter(name => name !== projectName);
|
|
783
798
|
if (newOrder.length !== order.length) {
|
|
784
|
-
saveProjectOrder(
|
|
799
|
+
saveProjectOrder(resolvedConfig, newOrder);
|
|
785
800
|
}
|
|
786
801
|
|
|
787
|
-
invalidateProjectsCache(
|
|
802
|
+
invalidateProjectsCache(resolvedConfig);
|
|
788
803
|
return { success: true };
|
|
789
804
|
}
|
|
790
805
|
|
|
791
806
|
// Search sessions for keyword
|
|
792
807
|
function searchSessions(config, projectName, keyword, contextLength = 15) {
|
|
793
|
-
const
|
|
808
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
809
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
794
810
|
|
|
795
811
|
if (!fs.existsSync(projectDir)) {
|
|
796
812
|
return [];
|