coding-tool-x 3.6.13 → 3.7.1
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 +45 -33
- package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
- package/dist/web/assets/Home-BwTaYu3q.js +1 -0
- package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
- package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
- package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
- package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
- package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-Btfvh0GG.js +2 -0
- package/dist/web/assets/index-CLNa6DhH.css +1 -0
- package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +3 -1
- package/src/commands/channels.js +85 -24
- package/src/commands/daemon.js +7 -4
- package/src/commands/doctor.js +17 -8
- package/src/commands/logs.js +46 -6
- package/src/commands/port-config.js +83 -75
- package/src/commands/proxy-control.js +59 -26
- package/src/commands/stats.js +37 -19
- package/src/commands/switch.js +5 -1
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/commands/workspace.js +3 -35
- package/src/config/default.js +19 -2
- package/src/config/loader.js +5 -76
- package/src/config/paths.js +96 -11
- package/src/index.js +10 -16
- package/src/server/api/commands.js +1 -1
- package/src/server/api/config-export.js +4 -2
- package/src/server/api/config-registry.js +40 -34
- package/src/server/api/config.js +5 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/omp-channels.js +349 -0
- package/src/server/api/omp-config.js +164 -0
- package/src/server/api/omp-projects.js +76 -0
- package/src/server/api/omp-proxy.js +154 -0
- package/src/server/api/omp-sessions.js +334 -0
- package/src/server/api/omp-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/settings.js +2 -0
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +3 -41
- package/src/server/index.js +41 -0
- package/src/server/omp-proxy-server.js +66 -0
- package/src/server/services/agents-service.js +2 -1
- package/src/server/services/base/proxy-utils.js +1 -0
- package/src/server/services/channel-balance.js +151 -1
- package/src/server/services/channel-scheduler.js +7 -1
- package/src/server/services/commands-service.js +11 -2
- package/src/server/services/config-export-service.js +296 -39
- package/src/server/services/config-registry-service.js +20 -18
- package/src/server/services/config-sync-manager.js +115 -6
- package/src/server/services/config-sync-service.js +4 -2
- package/src/server/services/config-templates-service.js +183 -45
- package/src/server/services/mcp-service.js +217 -33
- package/src/server/services/notification-hooks.js +138 -6
- package/src/server/services/omp-auth-providers.js +294 -0
- package/src/server/services/omp-channels.js +94 -0
- package/src/server/services/omp-config.js +269 -0
- package/src/server/services/omp-sessions.js +490 -0
- package/src/server/services/omp-settings-manager.js +975 -0
- package/src/server/services/omp-statistics-service.js +317 -0
- package/src/server/services/plugins-service.js +673 -11
- package/src/server/services/prompts-service.js +112 -7
- package/src/server/services/proxy-log-helper.js +1 -0
- package/src/server/services/proxy-runtime.js +5 -1
- package/src/server/services/sessions.js +19 -35
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +34 -28
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +117 -225
- package/src/server/websocket-server.js +7 -1
- package/src/shared/platforms.js +201 -0
- package/src/utils/session.js +4 -10
- package/dist/web/assets/Home-BiaKZMah.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
- package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
- package/dist/web/assets/index-BpBFF3b2.js +0 -2
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- package/src/utils/cli-flags.js +0 -7
|
@@ -16,17 +16,22 @@ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homed
|
|
|
16
16
|
const PROMPTS_FILE = PATHS.prompts;
|
|
17
17
|
|
|
18
18
|
// 各平台提示词文件路径
|
|
19
|
-
const
|
|
19
|
+
const CLAUDE_CONFIG_DIR = NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings);
|
|
20
|
+
const CLAUDE_PROMPT_PATH = NATIVE_PATHS.claude.prompt || path.join(CLAUDE_CONFIG_DIR, 'CLAUDE.md');
|
|
20
21
|
const CODEX_PROMPT_PATH = path.join(HOME_DIR, '.codex', 'AGENTS.md');
|
|
21
22
|
const GEMINI_PROMPT_PATH = path.join(HOME_DIR, '.gemini', 'GEMINI.md');
|
|
22
23
|
const OPENCODE_PROMPT_PATH = path.join(NATIVE_PATHS.opencode.config, 'AGENTS.md');
|
|
24
|
+
const OMP_PROMPTS_DIR = NATIVE_PATHS.omp?.prompts
|
|
25
|
+
|| path.join(NATIVE_PATHS.omp?.dir || path.join(HOME_DIR, '.omp', 'agent'), 'prompts');
|
|
26
|
+
const MANAGED_OMP_PROMPTS_DIR = path.join(OMP_PROMPTS_DIR, 'coding-tool-x');
|
|
23
27
|
|
|
24
|
-
function normalizeApps(apps = {}, defaults = { claude: true, codex: true, gemini: true, opencode: false }) {
|
|
28
|
+
function normalizeApps(apps = {}, defaults = { claude: true, codex: true, gemini: true, opencode: false, omp: false }) {
|
|
25
29
|
return {
|
|
26
30
|
claude: apps.claude !== undefined ? !!apps.claude : defaults.claude,
|
|
27
31
|
codex: apps.codex !== undefined ? !!apps.codex : defaults.codex,
|
|
28
32
|
gemini: apps.gemini !== undefined ? !!apps.gemini : defaults.gemini,
|
|
29
|
-
opencode: apps.opencode !== undefined ? !!apps.opencode : defaults.opencode
|
|
33
|
+
opencode: apps.opencode !== undefined ? !!apps.opencode : defaults.opencode,
|
|
34
|
+
omp: apps.omp !== undefined ? !!apps.omp: defaults.omp
|
|
30
35
|
};
|
|
31
36
|
}
|
|
32
37
|
|
|
@@ -211,7 +216,7 @@ function initPromptsData() {
|
|
|
211
216
|
name: '当前使用',
|
|
212
217
|
description: '从现有配置导入',
|
|
213
218
|
content: existingContent,
|
|
214
|
-
apps: normalizeApps({ claude: true, codex: false, gemini: false, opencode: false }),
|
|
219
|
+
apps: normalizeApps({ claude: true, codex: false, gemini: false, opencode: false, omp: false }),
|
|
215
220
|
isBuiltin: false,
|
|
216
221
|
isImported: true,
|
|
217
222
|
createdAt: Date.now(),
|
|
@@ -234,6 +239,7 @@ function initPromptsData() {
|
|
|
234
239
|
preset.apps.codex !== normalizedApps.codex ||
|
|
235
240
|
preset.apps.gemini !== normalizedApps.gemini ||
|
|
236
241
|
preset.apps.opencode !== normalizedApps.opencode
|
|
242
|
+
|| preset.apps.omp !== normalizedApps.omp
|
|
237
243
|
) {
|
|
238
244
|
preset.apps = normalizedApps;
|
|
239
245
|
updated = true;
|
|
@@ -307,6 +313,7 @@ function savePreset(preset) {
|
|
|
307
313
|
|
|
308
314
|
const data = initPromptsData();
|
|
309
315
|
const existing = data.presets[preset.id];
|
|
316
|
+
const wasActive = data.activePresetId === preset.id;
|
|
310
317
|
|
|
311
318
|
// 内置模板不允许修改
|
|
312
319
|
if (existing && existing.isBuiltin) {
|
|
@@ -325,6 +332,13 @@ function savePreset(preset) {
|
|
|
325
332
|
data.presets[preset.id] = preset;
|
|
326
333
|
writeJsonFile(PROMPTS_FILE, data);
|
|
327
334
|
|
|
335
|
+
if (wasActive) {
|
|
336
|
+
if (existing && normalizeApps(existing.apps).omp && !preset.apps.omp) {
|
|
337
|
+
deleteOmpPromptTemplate(existing);
|
|
338
|
+
}
|
|
339
|
+
syncPresetToAllPlatforms(preset);
|
|
340
|
+
}
|
|
341
|
+
|
|
328
342
|
return preset;
|
|
329
343
|
}
|
|
330
344
|
|
|
@@ -348,6 +362,10 @@ function deletePreset(id) {
|
|
|
348
362
|
data.activePresetId = null;
|
|
349
363
|
}
|
|
350
364
|
|
|
365
|
+
if (normalizeApps(preset.apps).omp) {
|
|
366
|
+
deleteOmpPromptTemplate(preset);
|
|
367
|
+
}
|
|
368
|
+
|
|
351
369
|
delete data.presets[id];
|
|
352
370
|
writeJsonFile(PROMPTS_FILE, data);
|
|
353
371
|
|
|
@@ -360,6 +378,7 @@ function deletePreset(id) {
|
|
|
360
378
|
async function activatePreset(id) {
|
|
361
379
|
const data = initPromptsData();
|
|
362
380
|
const preset = data.presets[id];
|
|
381
|
+
const previousPreset = data.activePresetId ? data.presets[data.activePresetId] : null;
|
|
363
382
|
|
|
364
383
|
if (!preset) {
|
|
365
384
|
throw new Error(`预设 "${id}" 不存在`);
|
|
@@ -369,6 +388,10 @@ async function activatePreset(id) {
|
|
|
369
388
|
data.activePresetId = id;
|
|
370
389
|
writeJsonFile(PROMPTS_FILE, data);
|
|
371
390
|
|
|
391
|
+
if (previousPreset && previousPreset.id !== id && normalizeApps(previousPreset.apps).omp) {
|
|
392
|
+
deleteOmpPromptTemplate(previousPreset);
|
|
393
|
+
}
|
|
394
|
+
|
|
372
395
|
// 同步到各平台
|
|
373
396
|
await syncPresetToAllPlatforms(preset);
|
|
374
397
|
|
|
@@ -380,6 +403,7 @@ async function activatePreset(id) {
|
|
|
380
403
|
*/
|
|
381
404
|
async function deactivatePrompt() {
|
|
382
405
|
const data = initPromptsData();
|
|
406
|
+
const activePreset = data.activePresetId ? data.presets[data.activePresetId] : null;
|
|
383
407
|
|
|
384
408
|
// 清空激活状态
|
|
385
409
|
data.activePresetId = null;
|
|
@@ -390,7 +414,10 @@ async function deactivatePrompt() {
|
|
|
390
414
|
claude: deleteFile(CLAUDE_PROMPT_PATH),
|
|
391
415
|
codex: deleteFile(CODEX_PROMPT_PATH),
|
|
392
416
|
gemini: deleteFile(GEMINI_PROMPT_PATH),
|
|
393
|
-
opencode: deleteFile(OPENCODE_PROMPT_PATH)
|
|
417
|
+
opencode: deleteFile(OPENCODE_PROMPT_PATH),
|
|
418
|
+
omp: activePreset && normalizeApps(activePreset.apps).omp
|
|
419
|
+
? deleteOmpPromptTemplate(activePreset)
|
|
420
|
+
: false
|
|
394
421
|
};
|
|
395
422
|
|
|
396
423
|
console.log('[Prompts] Deactivated and removed prompt files:', results);
|
|
@@ -405,7 +432,7 @@ async function deactivatePrompt() {
|
|
|
405
432
|
/**
|
|
406
433
|
* 同步预设到所有已启用的平台
|
|
407
434
|
*/
|
|
408
|
-
|
|
435
|
+
function syncPresetToAllPlatforms(preset) {
|
|
409
436
|
const apps = normalizeApps(preset.apps);
|
|
410
437
|
const { content } = preset;
|
|
411
438
|
|
|
@@ -428,6 +455,43 @@ async function syncPresetToAllPlatforms(preset) {
|
|
|
428
455
|
writeTextFile(OPENCODE_PROMPT_PATH, content);
|
|
429
456
|
console.log('[Prompts] Synced to OpenCode:', OPENCODE_PROMPT_PATH);
|
|
430
457
|
}
|
|
458
|
+
|
|
459
|
+
if (apps.omp) {
|
|
460
|
+
const targetPath = writeOmpPromptTemplate(preset);
|
|
461
|
+
console.log('[Prompts] Synced to OMP:', targetPath);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function buildOmpPromptFileName(preset) {
|
|
466
|
+
const rawId = (preset?.id || 'preset').toString().trim();
|
|
467
|
+
const safeId = rawId.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').slice(0, 100) || 'preset';
|
|
468
|
+
return `${safeId}.md`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function getOmpPromptTemplatePath(preset) {
|
|
472
|
+
return path.join(MANAGED_OMP_PROMPTS_DIR, buildOmpPromptFileName(preset));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function escapeFrontmatterValue(value) {
|
|
476
|
+
return String(value || '')
|
|
477
|
+
.replace(/\\/g, '\\\\')
|
|
478
|
+
.replace(/"/g, '\\"')
|
|
479
|
+
.replace(/\r?\n/g, ' ');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function buildOmpPromptTemplateContent(preset) {
|
|
483
|
+
const description = escapeFrontmatterValue(preset.description || preset.name || preset.id);
|
|
484
|
+
return `---\ndescription: "${description}"\n---\n\n${preset.content || ''}`;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function writeOmpPromptTemplate(preset) {
|
|
488
|
+
const filePath = getOmpPromptTemplatePath(preset);
|
|
489
|
+
writeTextFile(filePath, buildOmpPromptTemplateContent(preset));
|
|
490
|
+
return filePath;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function deleteOmpPromptTemplate(preset) {
|
|
494
|
+
return deleteFile(getOmpPromptTemplatePath(preset));
|
|
431
495
|
}
|
|
432
496
|
|
|
433
497
|
/**
|
|
@@ -443,11 +507,46 @@ function readPlatformPrompt(platform) {
|
|
|
443
507
|
return readTextFile(GEMINI_PROMPT_PATH, '');
|
|
444
508
|
case 'opencode':
|
|
445
509
|
return readTextFile(OPENCODE_PROMPT_PATH, '');
|
|
510
|
+
case 'omp':
|
|
511
|
+
throw new Error('OMP 使用 prompts/ 目录中的模板文件,请通过平台状态查看模板列表');
|
|
446
512
|
default:
|
|
447
513
|
throw new Error(`无效的平台: ${platform}`);
|
|
448
514
|
}
|
|
449
515
|
}
|
|
450
516
|
|
|
517
|
+
function listOmpPromptTemplates() {
|
|
518
|
+
if (!fs.existsSync(OMP_PROMPTS_DIR)) {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const results = [];
|
|
523
|
+
const walk = (dir) => {
|
|
524
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
525
|
+
if (entry.name.startsWith('.')) continue;
|
|
526
|
+
const fullPath = path.join(dir, entry.name);
|
|
527
|
+
if (entry.isDirectory()) {
|
|
528
|
+
walk(fullPath);
|
|
529
|
+
} else if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.md') {
|
|
530
|
+
const relativePath = path.relative(OMP_PROMPTS_DIR, fullPath);
|
|
531
|
+
results.push({
|
|
532
|
+
name: path.basename(entry.name, '.md'),
|
|
533
|
+
path: fullPath,
|
|
534
|
+
relativePath,
|
|
535
|
+
managed: relativePath.split(path.sep)[0] === 'coding-tool-x'
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
walk(OMP_PROMPTS_DIR);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
console.error(`[Prompts] Failed to list OMP prompt templates:`, err.message);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
548
|
+
}
|
|
549
|
+
|
|
451
550
|
/**
|
|
452
551
|
* 获取各平台当前的提示词状态
|
|
453
552
|
*/
|
|
@@ -472,6 +571,12 @@ function getPlatformStatus() {
|
|
|
472
571
|
path: OPENCODE_PROMPT_PATH,
|
|
473
572
|
exists: fs.existsSync(OPENCODE_PROMPT_PATH),
|
|
474
573
|
content: readTextFile(OPENCODE_PROMPT_PATH, '')
|
|
574
|
+
},
|
|
575
|
+
omp: {
|
|
576
|
+
path: OMP_PROMPTS_DIR,
|
|
577
|
+
managedPath: MANAGED_OMP_PROMPTS_DIR,
|
|
578
|
+
exists: fs.existsSync(OMP_PROMPTS_DIR),
|
|
579
|
+
templates: listOmpPromptTemplates()
|
|
475
580
|
}
|
|
476
581
|
};
|
|
477
582
|
}
|
|
@@ -494,7 +599,7 @@ function importFromPlatform(platform, presetName) {
|
|
|
494
599
|
name: presetName || `从 ${platform} 导入`,
|
|
495
600
|
description: `从 ${platform} 导入的提示词`,
|
|
496
601
|
content,
|
|
497
|
-
apps: normalizeApps({ claude: false, codex: false, gemini: false, opencode: false }),
|
|
602
|
+
apps: normalizeApps({ claude: false, codex: false, gemini: false, opencode: false, omp: false }),
|
|
498
603
|
isBuiltin: false,
|
|
499
604
|
createdAt: Date.now(),
|
|
500
605
|
updatedAt: Date.now()
|
|
@@ -9,6 +9,7 @@ function normalizeToolSource(source = '') {
|
|
|
9
9
|
if (normalized === 'codex') return 'codex';
|
|
10
10
|
if (normalized === 'gemini') return 'gemini';
|
|
11
11
|
if (normalized === 'opencode') return 'opencode';
|
|
12
|
+
if (normalized === 'omp' || normalized === 'omp-agent') return 'omp';
|
|
12
13
|
return 'claude';
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -11,7 +11,11 @@ const PROXY_START_LOG_PATTERNS = {
|
|
|
11
11
|
],
|
|
12
12
|
codex: [/Codex proxy server started on http:\/\/127\.0\.0\.1:\d+/],
|
|
13
13
|
gemini: [/Gemini proxy server started on http:\/\/127\.0\.0\.1:\d+/],
|
|
14
|
-
opencode: [/OpenCode proxy server started on http:\/\/127\.0\.0\.1:\d+/]
|
|
14
|
+
opencode: [/OpenCode proxy server started on http:\/\/127\.0\.0\.1:\d+/],
|
|
15
|
+
omp: [
|
|
16
|
+
/OMP models\.yml provider config enabled, port: \d+/,
|
|
17
|
+
/OMP models\.yml 受管 provider 已自动启用,端口: \d+/
|
|
18
|
+
]
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
function getRuntimeFilePath(proxyType) {
|
|
@@ -17,17 +17,6 @@ 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
|
-
|
|
31
20
|
// Base directory for cc-tool data
|
|
32
21
|
function getCcToolDir() {
|
|
33
22
|
return PATHS.base;
|
|
@@ -193,7 +182,7 @@ function sliceClaudeSessionContentByUserMessage(content, afterUserMessageNumber)
|
|
|
193
182
|
|
|
194
183
|
// Get all projects with stats (async version)
|
|
195
184
|
async function getProjects(config) {
|
|
196
|
-
const projectsDir =
|
|
185
|
+
const projectsDir = config.projectsDir;
|
|
197
186
|
|
|
198
187
|
if (!fs.existsSync(projectsDir)) {
|
|
199
188
|
return [];
|
|
@@ -394,17 +383,16 @@ function extractCwdFromSessionHeader(sessionFile) {
|
|
|
394
383
|
|
|
395
384
|
// Get projects with detailed stats (with caching) - async version
|
|
396
385
|
async function getProjectsWithStats(config, options = {}) {
|
|
397
|
-
const resolvedConfig = withResolvedProjectsDir(config);
|
|
398
386
|
if (!options.force) {
|
|
399
387
|
// Check enhanced cache first
|
|
400
|
-
const cacheKey = `${CacheKeys.PROJECTS}${
|
|
388
|
+
const cacheKey = `${CacheKeys.PROJECTS}${config.projectsDir}`;
|
|
401
389
|
const enhancedCached = globalCache.get(cacheKey);
|
|
402
390
|
if (enhancedCached) {
|
|
403
391
|
return enhancedCached;
|
|
404
392
|
}
|
|
405
393
|
|
|
406
394
|
// Check old cache
|
|
407
|
-
const cached = getCachedProjects(
|
|
395
|
+
const cached = getCachedProjects(config);
|
|
408
396
|
if (cached) {
|
|
409
397
|
globalCache.set(cacheKey, cached, 300000); // 5分钟
|
|
410
398
|
return cached;
|
|
@@ -412,22 +400,22 @@ async function getProjectsWithStats(config, options = {}) {
|
|
|
412
400
|
}
|
|
413
401
|
|
|
414
402
|
try {
|
|
415
|
-
const data = await buildProjectsWithStats(
|
|
403
|
+
const data = await buildProjectsWithStats(config);
|
|
416
404
|
if (!Array.isArray(data)) {
|
|
417
|
-
console.warn(`[getProjectsWithStats] Unexpected non-array result for ${
|
|
405
|
+
console.warn(`[getProjectsWithStats] Unexpected non-array result for ${config.projectsDir}, returning empty array.`);
|
|
418
406
|
return [];
|
|
419
407
|
}
|
|
420
|
-
setCachedProjects(
|
|
421
|
-
globalCache.set(`${CacheKeys.PROJECTS}${
|
|
408
|
+
setCachedProjects(config, data);
|
|
409
|
+
globalCache.set(`${CacheKeys.PROJECTS}${config.projectsDir}`, data, 300000);
|
|
422
410
|
return data;
|
|
423
411
|
} catch (err) {
|
|
424
|
-
console.error(`[getProjectsWithStats] Failed to build projects for ${
|
|
412
|
+
console.error(`[getProjectsWithStats] Failed to build projects for ${config.projectsDir}:`, err);
|
|
425
413
|
return [];
|
|
426
414
|
}
|
|
427
415
|
}
|
|
428
416
|
|
|
429
417
|
async function buildProjectsWithStats(config) {
|
|
430
|
-
const projectsDir =
|
|
418
|
+
const projectsDir = config.projectsDir;
|
|
431
419
|
|
|
432
420
|
if (!fs.existsSync(projectsDir)) {
|
|
433
421
|
return [];
|
|
@@ -494,7 +482,7 @@ async function buildProjectsWithStats(config) {
|
|
|
494
482
|
|
|
495
483
|
// 获取 Claude 项目/会话数量(轻量统计)
|
|
496
484
|
function getProjectAndSessionCounts(config) {
|
|
497
|
-
const projectsDir =
|
|
485
|
+
const projectsDir = config.projectsDir;
|
|
498
486
|
if (!fs.existsSync(projectsDir)) {
|
|
499
487
|
return { projectCount: 0, sessionCount: 0 };
|
|
500
488
|
}
|
|
@@ -682,8 +670,7 @@ async function getSessionsForProject(config, projectName) {
|
|
|
682
670
|
|
|
683
671
|
// Delete a session
|
|
684
672
|
function deleteSession(config, projectName, sessionId) {
|
|
685
|
-
const
|
|
686
|
-
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
673
|
+
const projectDir = path.join(config.projectsDir, projectName);
|
|
687
674
|
const sessionFile = path.join(projectDir, sessionId + '.jsonl');
|
|
688
675
|
|
|
689
676
|
if (!fs.existsSync(sessionFile)) {
|
|
@@ -691,15 +678,14 @@ function deleteSession(config, projectName, sessionId) {
|
|
|
691
678
|
}
|
|
692
679
|
|
|
693
680
|
fs.unlinkSync(sessionFile);
|
|
694
|
-
invalidateProjectsCache(
|
|
681
|
+
invalidateProjectsCache(config);
|
|
695
682
|
invalidateSessionResultCache(projectName);
|
|
696
683
|
return { success: true };
|
|
697
684
|
}
|
|
698
685
|
|
|
699
686
|
// Fork a session
|
|
700
687
|
function forkSession(config, projectName, sessionId, options = {}) {
|
|
701
|
-
const
|
|
702
|
-
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
688
|
+
const projectDir = path.join(config.projectsDir, projectName);
|
|
703
689
|
const sessionFile = path.join(projectDir, sessionId + '.jsonl');
|
|
704
690
|
|
|
705
691
|
if (!fs.existsSync(sessionFile)) {
|
|
@@ -728,7 +714,7 @@ function forkSession(config, projectName, sessionId, options = {}) {
|
|
|
728
714
|
const { setAlias } = require('./alias');
|
|
729
715
|
setAlias(newSessionId, options.alias);
|
|
730
716
|
}
|
|
731
|
-
invalidateProjectsCache(
|
|
717
|
+
invalidateProjectsCache(config);
|
|
732
718
|
invalidateSessionResultCache(projectName);
|
|
733
719
|
|
|
734
720
|
return {
|
|
@@ -780,8 +766,7 @@ function saveSessionOrder(projectName, order) {
|
|
|
780
766
|
|
|
781
767
|
// Delete a project (remove the entire project directory)
|
|
782
768
|
function deleteProject(config, projectName) {
|
|
783
|
-
const
|
|
784
|
-
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
769
|
+
const projectDir = path.join(config.projectsDir, projectName);
|
|
785
770
|
|
|
786
771
|
if (!fs.existsSync(projectDir)) {
|
|
787
772
|
throw new Error('Project not found');
|
|
@@ -791,20 +776,19 @@ function deleteProject(config, projectName) {
|
|
|
791
776
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
792
777
|
|
|
793
778
|
// Remove from order file if exists
|
|
794
|
-
const order = getProjectOrder(
|
|
779
|
+
const order = getProjectOrder(config);
|
|
795
780
|
const newOrder = order.filter(name => name !== projectName);
|
|
796
781
|
if (newOrder.length !== order.length) {
|
|
797
|
-
saveProjectOrder(
|
|
782
|
+
saveProjectOrder(config, newOrder);
|
|
798
783
|
}
|
|
799
784
|
|
|
800
|
-
invalidateProjectsCache(
|
|
785
|
+
invalidateProjectsCache(config);
|
|
801
786
|
return { success: true };
|
|
802
787
|
}
|
|
803
788
|
|
|
804
789
|
// Search sessions for keyword
|
|
805
790
|
function searchSessions(config, projectName, keyword, contextLength = 15) {
|
|
806
|
-
const
|
|
807
|
-
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
791
|
+
const projectDir = path.join(config.projectsDir, projectName);
|
|
808
792
|
|
|
809
793
|
if (!fs.existsSync(projectDir)) {
|
|
810
794
|
return [];
|
|
@@ -25,7 +25,7 @@ const {
|
|
|
25
25
|
resolveInsideRoot
|
|
26
26
|
} = require('./config-artifact-paths');
|
|
27
27
|
|
|
28
|
-
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
28
|
+
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
|
|
29
29
|
const SUPPORTED_REPO_PROVIDERS = ['github', 'gitlab', 'local'];
|
|
30
30
|
const DEFAULT_GITHUB_HOST = 'https://github.com';
|
|
31
31
|
const DEFAULT_GITLAB_HOST = 'https://gitlab.com';
|
|
@@ -173,12 +173,16 @@ const DEFAULT_REPOS_BY_PLATFORM = {
|
|
|
173
173
|
claude: [],
|
|
174
174
|
codex: [],
|
|
175
175
|
gemini: [],
|
|
176
|
-
opencode: []
|
|
176
|
+
opencode: [],
|
|
177
|
+
omp: []
|
|
177
178
|
};
|
|
178
179
|
|
|
180
|
+
const CLAUDE_SKILLS_DIR = NATIVE_PATHS.claude.skills
|
|
181
|
+
|| path.join(NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings), 'skills');
|
|
182
|
+
|
|
179
183
|
const PLATFORM_CONFIG = {
|
|
180
184
|
claude: {
|
|
181
|
-
installDir:
|
|
185
|
+
installDir: CLAUDE_SKILLS_DIR,
|
|
182
186
|
storageDir: PATHS.localSkills.claude,
|
|
183
187
|
reposFile: PATHS.skillRepos.claude,
|
|
184
188
|
cacheFile: PATHS.skillCaches.claude
|
|
@@ -200,6 +204,12 @@ const PLATFORM_CONFIG = {
|
|
|
200
204
|
storageDir: PATHS.localSkills.opencode,
|
|
201
205
|
reposFile: PATHS.skillRepos.opencode,
|
|
202
206
|
cacheFile: PATHS.skillCaches.opencode
|
|
207
|
+
},
|
|
208
|
+
omp: {
|
|
209
|
+
installDir: NATIVE_PATHS.omp.skills,
|
|
210
|
+
storageDir: PATHS.localSkills.omp,
|
|
211
|
+
reposFile: PATHS.skillRepos.omp,
|
|
212
|
+
cacheFile: PATHS.skillCaches.omp
|
|
203
213
|
}
|
|
204
214
|
};
|
|
205
215
|
|
|
@@ -13,6 +13,7 @@ const { getEffectiveApiKey: getClaudeEffectiveApiKey } = require('./channels');
|
|
|
13
13
|
const { getEffectiveApiKey: getCodexEffectiveApiKey } = require('./codex-channels');
|
|
14
14
|
const { getEffectiveApiKey: getGeminiEffectiveApiKey } = require('./gemini-channels');
|
|
15
15
|
const { getEffectiveApiKey: getOpenCodeEffectiveApiKey } = require('./opencode-channels');
|
|
16
|
+
const { getEffectiveApiKey: getOmpEffectiveApiKey } = require('./omp-channels');
|
|
16
17
|
const { loadClaudeRequestTemplate } = require('./request-logger');
|
|
17
18
|
|
|
18
19
|
// 测试结果缓存
|
|
@@ -102,6 +103,8 @@ function resolveExplicitModel(channel, model) {
|
|
|
102
103
|
|
|
103
104
|
function resolveEffectiveApiKey(channel, channelType) {
|
|
104
105
|
switch (channelType) {
|
|
106
|
+
case 'omp':
|
|
107
|
+
return getOmpEffectiveApiKey(channel);
|
|
105
108
|
case 'codex':
|
|
106
109
|
return getCodexEffectiveApiKey(channel);
|
|
107
110
|
case 'gemini':
|
|
@@ -376,11 +379,14 @@ function extractJsonPayloads(responseData) {
|
|
|
376
379
|
* 测试单个渠道的连接速度和 API 功能
|
|
377
380
|
* @param {Object} channel - 渠道配置
|
|
378
381
|
* @param {number} timeout - 超时时间(毫秒)
|
|
379
|
-
* @param {string} channelType -
|
|
382
|
+
* @param {string} channelType - 请求格式类型:'claude' | 'codex' | 'gemini' | 'opencode'
|
|
383
|
+
* @param {Object} options - 可选行为
|
|
384
|
+
* @param {string} options.authSourceType - API key 来源平台;OMP 使用 'omp',请求格式仍由 channelType 决定
|
|
380
385
|
* @returns {Promise<Object>} 测试结果
|
|
381
386
|
*/
|
|
382
|
-
async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType = 'claude') {
|
|
387
|
+
async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType = 'claude', options = {}) {
|
|
383
388
|
const sanitizedTimeout = sanitizeTimeout(timeout);
|
|
389
|
+
const authSourceType = options?.authSourceType || channelType;
|
|
384
390
|
|
|
385
391
|
try {
|
|
386
392
|
if (!channel.baseUrl) {
|
|
@@ -416,7 +422,7 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
|
|
|
416
422
|
};
|
|
417
423
|
}
|
|
418
424
|
|
|
419
|
-
const effectiveApiKey = resolveEffectiveApiKey(channel,
|
|
425
|
+
const effectiveApiKey = resolveEffectiveApiKey(channel, authSourceType);
|
|
420
426
|
if (!effectiveApiKey) {
|
|
421
427
|
return {
|
|
422
428
|
channelId: channel.id,
|
|
@@ -433,7 +439,7 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
|
|
|
433
439
|
|
|
434
440
|
// 直接测试 API 功能(发送测试消息)
|
|
435
441
|
// 不再单独测试网络连通性,因为直接 GET base_url 可能返回 404
|
|
436
|
-
const
|
|
442
|
+
const aompResult = await testAPIFunctionality(
|
|
437
443
|
testUrl,
|
|
438
444
|
effectiveApiKey,
|
|
439
445
|
sanitizedTimeout,
|
|
@@ -442,8 +448,8 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
|
|
|
442
448
|
channel
|
|
443
449
|
);
|
|
444
450
|
|
|
445
|
-
const success =
|
|
446
|
-
const networkOk =
|
|
451
|
+
const success = aompResult.success;
|
|
452
|
+
const networkOk = aompResult.latency !== null; // 如果有延迟数据,说明网络是通的
|
|
447
453
|
|
|
448
454
|
// 缓存结果
|
|
449
455
|
const finalResult = {
|
|
@@ -452,13 +458,13 @@ async function testChannelSpeed(channel, timeout = DEFAULT_TIMEOUT, channelType
|
|
|
452
458
|
success,
|
|
453
459
|
networkOk,
|
|
454
460
|
apiOk: success,
|
|
455
|
-
statusCode:
|
|
456
|
-
error: success ? null : (
|
|
457
|
-
latency:
|
|
461
|
+
statusCode: aompResult.statusCode || null,
|
|
462
|
+
error: success ? null : (aompResult.error || '测试失败'),
|
|
463
|
+
latency: aompResult.latency ?? null, // 无论成功失败都保留延迟数据(保留 0ms)
|
|
458
464
|
testedAt: Date.now(),
|
|
459
|
-
testedModel:
|
|
460
|
-
availableModels:
|
|
461
|
-
modelDetectionMethod:
|
|
465
|
+
testedModel: aompResult.testedModel,
|
|
466
|
+
availableModels: aompResult.availableModels,
|
|
467
|
+
modelDetectionMethod: aompResult.modelDetectionMethod
|
|
462
468
|
};
|
|
463
469
|
|
|
464
470
|
testResultsCache.set(channel.id, finalResult);
|
|
@@ -655,11 +661,11 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
655
661
|
|
|
656
662
|
if (channelType === 'claude') {
|
|
657
663
|
// Anthropic Messages API - 模拟 Claude Code 请求格式
|
|
658
|
-
let
|
|
659
|
-
if (!
|
|
660
|
-
|
|
664
|
+
let aompPath = parsedUrl.pathname.replace(/\/$/, '');
|
|
665
|
+
if (!aompPath.endsWith('/messages')) {
|
|
666
|
+
aompPath = aompPath + (aompPath.endsWith('/v1') ? '/messages' : '/v1/messages');
|
|
661
667
|
}
|
|
662
|
-
|
|
668
|
+
aompPath += '?beta=true';
|
|
663
669
|
|
|
664
670
|
testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'claude-sonnet-4-20250514';
|
|
665
671
|
const sessionId = typeof crypto.randomUUID === 'function'
|
|
@@ -680,20 +686,20 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
680
686
|
tools: toolsToUse
|
|
681
687
|
};
|
|
682
688
|
primaryRequestConfig = {
|
|
683
|
-
|
|
689
|
+
aompPath,
|
|
684
690
|
requestBody: JSON.stringify(requestPayload),
|
|
685
691
|
headers: buildClaudeRequestHeaders(apiKey, { hasTools: true }),
|
|
686
692
|
isStreamingResponse: true
|
|
687
693
|
};
|
|
688
694
|
// Fallback: non-streaming for gateways that don't support SSE
|
|
689
695
|
fallbackRequestConfig = {
|
|
690
|
-
|
|
696
|
+
aompPath,
|
|
691
697
|
requestBody: JSON.stringify({ ...requestPayload, stream: false }),
|
|
692
698
|
headers: buildClaudeRequestHeaders(apiKey, { hasTools: true }),
|
|
693
699
|
isStreamingResponse: false
|
|
694
700
|
};
|
|
695
701
|
} else if (channelType === 'codex') {
|
|
696
|
-
const
|
|
702
|
+
const aompPath = buildCodexResponsesPath(parsedUrl);
|
|
697
703
|
testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-5-codex';
|
|
698
704
|
const codexSessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
699
705
|
|
|
@@ -706,7 +712,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
706
712
|
};
|
|
707
713
|
|
|
708
714
|
primaryRequestConfig = {
|
|
709
|
-
|
|
715
|
+
aompPath,
|
|
710
716
|
requestBody: JSON.stringify({ ...baseBody, stream: false }),
|
|
711
717
|
headers: {
|
|
712
718
|
'Authorization': `Bearer ${apiKey || ''}`,
|
|
@@ -723,7 +729,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
723
729
|
};
|
|
724
730
|
|
|
725
731
|
fallbackRequestConfig = {
|
|
726
|
-
|
|
732
|
+
aompPath,
|
|
727
733
|
requestBody: JSON.stringify({ ...baseBody, stream: true }),
|
|
728
734
|
headers: {
|
|
729
735
|
...primaryRequestConfig.headers,
|
|
@@ -736,7 +742,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
736
742
|
const useCliFormat = shouldUseGeminiCliFormat(parsedUrl);
|
|
737
743
|
|
|
738
744
|
const cliRequestConfig = {
|
|
739
|
-
|
|
745
|
+
aompPath: buildGeminiCliGeneratePath(parsedUrl),
|
|
740
746
|
requestBody: JSON.stringify({
|
|
741
747
|
project: '',
|
|
742
748
|
model: testModel,
|
|
@@ -758,7 +764,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
758
764
|
};
|
|
759
765
|
|
|
760
766
|
const nativeRequestConfig = {
|
|
761
|
-
|
|
767
|
+
aompPath: buildGeminiNativeGeneratePath(parsedUrl, testModel),
|
|
762
768
|
requestBody: JSON.stringify({
|
|
763
769
|
contents: [{ role: 'user', parts: [{ text: 'ping' }] }],
|
|
764
770
|
generationConfig: { maxOutputTokens: 1, temperature: 0 }
|
|
@@ -777,12 +783,12 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
777
783
|
fallbackRequestConfig = useCliFormat ? nativeRequestConfig : cliRequestConfig;
|
|
778
784
|
} else {
|
|
779
785
|
testModel = modelProbe?.preferredTestModel || normalizeNonEmptyString(model) || 'gpt-4o-mini';
|
|
780
|
-
let
|
|
781
|
-
if (!
|
|
782
|
-
|
|
786
|
+
let aompPath = parsedUrl.pathname.replace(/\/$/, '');
|
|
787
|
+
if (!aompPath.endsWith('/chat/completions')) {
|
|
788
|
+
aompPath = aompPath + (aompPath.endsWith('/v1') ? '/chat/completions' : '/v1/chat/completions');
|
|
783
789
|
}
|
|
784
790
|
primaryRequestConfig = {
|
|
785
|
-
|
|
791
|
+
aompPath,
|
|
786
792
|
requestBody: JSON.stringify({
|
|
787
793
|
model: testModel,
|
|
788
794
|
max_tokens: 1,
|
|
@@ -802,7 +808,7 @@ async function testAPIFunctionality(baseUrl, apiKey, timeout, channelType = 'cla
|
|
|
802
808
|
const options = {
|
|
803
809
|
hostname: parsedUrl.hostname,
|
|
804
810
|
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
805
|
-
path: requestConfig.
|
|
811
|
+
path: requestConfig.aompPath,
|
|
806
812
|
method: 'POST',
|
|
807
813
|
timeout,
|
|
808
814
|
headers: requestConfig.headers
|