coding-tool-x 3.5.8 → 3.5.10
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/CHANGELOG.md +11 -0
- package/README.md +4 -25
- package/bin/ctx.js +1 -6
- package/dist/web/assets/{Analytics-BzoNzfbi.js → Analytics-DPb1zErn.js} +2 -2
- package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
- package/dist/web/assets/{ConfigTemplates-O4ikBt1o.js → ConfigTemplates-Bki2k4Lo.js} +1 -1
- package/dist/web/assets/{Home-BQjsnblU.js → Home-BKYHKSax.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/{PluginManager-DS_DJnVc.js → PluginManager-jgeQOE24.js} +1 -1
- package/dist/web/assets/ProjectList-CF67RzP_.js +1 -0
- package/dist/web/assets/ProjectList-DL4JK6ci.css +1 -0
- package/dist/web/assets/SessionList-DhHsY5q9.js +1 -0
- package/dist/web/assets/SessionList-lz23U6ZJ.css +1 -0
- package/dist/web/assets/{SkillManager-DpNE02r0.js → SkillManager-CtEz5gGp.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-DMY7_SHh.js → WorkspaceManager-D3B0Y5XE.js} +1 -1
- package/dist/web/assets/icons-lKCTySl7.js +1 -0
- package/dist/web/assets/index-BS7aplYp.css +1 -0
- package/dist/web/assets/index-BnyNm74G.js +2 -0
- package/dist/web/assets/{markdown-DyTJGI4N.js → markdown-BNJfEgCA.js} +1 -1
- package/dist/web/assets/{naive-ui-Cg4_ZeoT.js → naive-ui-DM897mIP.js} +1 -1
- package/dist/web/assets/vendors-DQA4Hw9v.js +7 -0
- package/dist/web/assets/vue-vendor-B5RqjEjQ.js +45 -0
- package/dist/web/index.html +8 -8
- package/package.json +1 -2
- package/src/commands/daemon.js +5 -33
- package/src/commands/export-config.js +6 -6
- package/src/commands/stats.js +4 -41
- package/src/commands/ui.js +3 -12
- package/src/config/channel-preset-websites.js +114 -0
- package/src/config/default.js +9 -5
- package/src/config/loader.js +2 -2
- package/src/config/model-metadata.json +16 -20
- package/src/config/paths.js +33 -166
- package/src/index.js +34 -125
- package/src/server/api/agents.js +2 -52
- package/src/server/api/codex-proxy.js +4 -2
- package/src/server/api/codex-sessions.js +88 -9
- package/src/server/api/commands.js +2 -38
- package/src/server/api/dashboard.js +1 -25
- package/src/server/api/gemini-proxy.js +4 -2
- package/src/server/api/gemini-sessions.js +86 -9
- package/src/server/api/hooks.js +1 -17
- package/src/server/api/opencode-proxy.js +7 -2
- package/src/server/api/opencode-sessions.js +82 -7
- package/src/server/api/plugins.js +34 -128
- package/src/server/api/proxy.js +5 -3
- package/src/server/api/sessions.js +180 -24
- package/src/server/api/workspaces.js +0 -14
- package/src/server/index.js +9 -31
- package/src/server/services/agents-service.js +62 -269
- package/src/server/services/base/base-channel-service.js +3 -1
- package/src/server/services/channels.js +5 -0
- package/src/server/services/codex-channels.js +2 -0
- package/src/server/services/codex-sessions.js +78 -108
- package/src/server/services/commands-service.js +81 -281
- package/src/server/services/config-export-service.js +7 -7
- package/src/server/services/config-registry-service.js +9 -8
- package/src/server/services/config-sync-manager.js +41 -61
- package/src/server/services/config-sync-service.js +3 -3
- package/src/server/services/gemini-channels.js +17 -6
- package/src/server/services/gemini-config.js +4 -3
- package/src/server/services/gemini-sessions.js +51 -26
- package/src/server/services/gemini-settings-manager.js +3 -2
- package/src/server/services/mcp-service.js +14 -9
- package/src/server/services/native-oauth-adapters.js +3 -3
- package/src/server/services/network-access.js +2 -61
- package/src/server/services/notification-hooks.js +19 -184
- package/src/server/services/opencode-channels.js +9 -0
- package/src/server/services/opencode-sessions.js +4 -4
- package/src/server/services/opencode-settings-manager.js +3 -3
- package/src/server/services/plugins-service.js +62 -996
- package/src/server/services/prompts-service.js +9 -5
- package/src/server/services/sessions.js +128 -99
- package/src/server/services/skill-service.js +18 -155
- package/src/server/websocket-server.js +4 -35
- package/dist/web/assets/Analytics-vQS5IWvs.css +0 -1
- package/dist/web/assets/Home-qzk118Of.css +0 -1
- package/dist/web/assets/ProjectList-CqYDtsHx.js +0 -1
- package/dist/web/assets/ProjectList-GCC2QOmq.css +0 -1
- package/dist/web/assets/SessionList-CfPtcq6Y.css +0 -1
- package/dist/web/assets/SessionList-DMlLtMCz.js +0 -1
- package/dist/web/assets/icons-CEq2hYB-.js +0 -1
- package/dist/web/assets/index-Clf0l3wc.js +0 -2
- package/dist/web/assets/index-Dih_bOsv.css +0 -1
- package/dist/web/assets/vendors-Bsp-dq2d.js +0 -7
- package/dist/web/assets/vue-vendor-BxIT0uQq.js +0 -45
- package/docs/Caddyfile.example +0 -19
- package/docs/reverse-proxy-https.md +0 -57
- package/src/server/services/https-cert.js +0 -171
- package/src/server/services/session-launch-command.js +0 -81
- package/src/server/services/web-ui-runtime.js +0 -54
|
@@ -7,42 +7,32 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
10
11
|
const toml = require('toml');
|
|
11
12
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
12
13
|
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
PATHS,
|
|
16
|
-
HOME_DIR,
|
|
17
|
-
isWindowsAbsolutePath,
|
|
18
|
-
isNativeAbsolutePath,
|
|
19
|
-
joinNativeBasePath,
|
|
20
|
-
resolveNativeBasePath,
|
|
21
|
-
getNativePathDir
|
|
22
|
-
} = require('../../config/paths');
|
|
14
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
15
|
+
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
23
16
|
|
|
24
17
|
// 默认仓库源
|
|
25
18
|
const DEFAULT_REPOS = [];
|
|
26
19
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'opencode'];
|
|
27
|
-
const
|
|
28
|
-
const OPENCODE_LEGACY_AGENTS_DIR = NATIVE_PATHS.opencode.agentsLegacy;
|
|
29
|
-
const CODEX_DIR = NATIVE_PATHS.codex.dir;
|
|
20
|
+
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
30
21
|
const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
22
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
23
|
+
const CODEX_AGENTS_DIR = path.join(path.dirname(CODEX_CONFIG_PATH), 'agents');
|
|
24
|
+
const CLAUDE_AGENTS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'agents');
|
|
33
25
|
const CODEX_CONFIG_MODES = new Set(['none', 'managed', 'custom']);
|
|
34
26
|
|
|
35
27
|
const PLATFORM_CONFIG = {
|
|
36
28
|
claude: {
|
|
37
29
|
userAgentsDir: CLAUDE_AGENTS_DIR,
|
|
38
|
-
storageDir: PATHS.localAgents.claude,
|
|
39
30
|
projectAgentsDir: (projectPath) => path.join(projectPath, '.claude', 'agents'),
|
|
40
31
|
repoType: 'agents'
|
|
41
32
|
},
|
|
42
33
|
opencode: {
|
|
43
|
-
userAgentsDir:
|
|
44
|
-
|
|
45
|
-
legacyUserAgentsDir: OPENCODE_LEGACY_AGENTS_DIR,
|
|
34
|
+
userAgentsDir: path.join(OPENCODE_CONFIG_DIR, 'agents'),
|
|
35
|
+
legacyUserAgentsDir: path.join(OPENCODE_CONFIG_DIR, 'agent'),
|
|
46
36
|
projectAgentsDir: (projectPath) => {
|
|
47
37
|
const modern = path.join(projectPath, '.opencode', 'agents');
|
|
48
38
|
const legacy = path.join(projectPath, '.opencode', 'agent');
|
|
@@ -55,7 +45,6 @@ const PLATFORM_CONFIG = {
|
|
|
55
45
|
},
|
|
56
46
|
codex: {
|
|
57
47
|
userAgentsDir: CODEX_AGENTS_DIR,
|
|
58
|
-
storageDir: PATHS.localAgents.codex,
|
|
59
48
|
projectAgentsDir: () => null,
|
|
60
49
|
repoType: 'agents'
|
|
61
50
|
}
|
|
@@ -160,7 +149,7 @@ function readCodexTomlConfig() {
|
|
|
160
149
|
}
|
|
161
150
|
|
|
162
151
|
function writeCodexTomlConfig(config) {
|
|
163
|
-
ensureDir(
|
|
152
|
+
ensureDir(path.dirname(CODEX_CONFIG_PATH));
|
|
164
153
|
writeFileAtomic(CODEX_CONFIG_PATH, tomlStringify(config));
|
|
165
154
|
}
|
|
166
155
|
|
|
@@ -169,7 +158,7 @@ function isPlainObject(value) {
|
|
|
169
158
|
}
|
|
170
159
|
|
|
171
160
|
function getCodexManagedAgentConfigPath(fileName) {
|
|
172
|
-
return
|
|
161
|
+
return path.join(CODEX_AGENTS_DIR, `${fileName}.toml`);
|
|
173
162
|
}
|
|
174
163
|
|
|
175
164
|
function normalizeCodexConfigPath(configPath) {
|
|
@@ -198,7 +187,7 @@ function assertSafeCodexConfigPath(configPath) {
|
|
|
198
187
|
return `~/${relative}`;
|
|
199
188
|
}
|
|
200
189
|
|
|
201
|
-
if (
|
|
190
|
+
if (path.isAbsolute(normalized)) {
|
|
202
191
|
return normalized;
|
|
203
192
|
}
|
|
204
193
|
|
|
@@ -220,35 +209,21 @@ function resolveCodexConfigPath(configPath) {
|
|
|
220
209
|
if (!normalized) return '';
|
|
221
210
|
|
|
222
211
|
if (normalized.startsWith('~/')) {
|
|
223
|
-
return
|
|
212
|
+
return path.join(HOME_DIR, normalized.slice(2));
|
|
224
213
|
}
|
|
225
214
|
|
|
226
|
-
if (
|
|
215
|
+
if (path.isAbsolute(normalized)) {
|
|
227
216
|
return normalized;
|
|
228
217
|
}
|
|
229
218
|
|
|
230
|
-
return
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function isPathInsideNativeDir(targetPath, baseDir) {
|
|
234
|
-
const shouldFoldCase = isWindowsAbsolutePath(baseDir);
|
|
235
|
-
const normalize = (value) => {
|
|
236
|
-
const normalized = String(value || '')
|
|
237
|
-
.replace(/[\\/]+/g, '/')
|
|
238
|
-
.replace(/\/+$/, '');
|
|
239
|
-
return shouldFoldCase ? normalized.toLowerCase() : normalized;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const normalizedTarget = normalize(targetPath);
|
|
243
|
-
const normalizedBase = normalize(baseDir);
|
|
244
|
-
return normalizedTarget === normalizedBase || normalizedTarget.startsWith(`${normalizedBase}/`);
|
|
219
|
+
return path.resolve(path.dirname(CODEX_CONFIG_PATH), normalized);
|
|
245
220
|
}
|
|
246
221
|
|
|
247
222
|
function isManagedCodexConfigPath(configPath) {
|
|
248
223
|
const resolved = resolveCodexConfigPath(configPath);
|
|
249
224
|
if (!resolved) return false;
|
|
250
|
-
const managedRoot =
|
|
251
|
-
return
|
|
225
|
+
const managedRoot = path.resolve(CODEX_AGENTS_DIR) + path.sep;
|
|
226
|
+
return resolved.startsWith(managedRoot) || resolved === path.resolve(CODEX_AGENTS_DIR);
|
|
252
227
|
}
|
|
253
228
|
|
|
254
229
|
function getManagedCodexConfigResolvedPath(configPath) {
|
|
@@ -412,7 +387,7 @@ function generateFrontmatter(data, platform = 'claude') {
|
|
|
412
387
|
/**
|
|
413
388
|
* 扫描目录获取代理文件(agents 约定为扁平目录)
|
|
414
389
|
*/
|
|
415
|
-
function scanAgentsDir(dir, basePath, scope
|
|
390
|
+
function scanAgentsDir(dir, basePath, scope) {
|
|
416
391
|
const agents = [];
|
|
417
392
|
|
|
418
393
|
if (!fs.existsSync(dir)) {
|
|
@@ -435,7 +410,7 @@ function scanAgentsDir(dir, basePath, scope, options = {}) {
|
|
|
435
410
|
const relativePath = path.relative(basePath, fullPath);
|
|
436
411
|
const fileName = entry.name.replace(/\.md$/, '');
|
|
437
412
|
|
|
438
|
-
|
|
413
|
+
agents.push({
|
|
439
414
|
name: frontmatter.name || fileName,
|
|
440
415
|
fileName,
|
|
441
416
|
scope,
|
|
@@ -449,15 +424,7 @@ function scanAgentsDir(dir, basePath, scope, options = {}) {
|
|
|
449
424
|
systemPrompt: body,
|
|
450
425
|
fullContent: content,
|
|
451
426
|
updatedAt: fs.statSync(fullPath).mtime.getTime()
|
|
452
|
-
};
|
|
453
|
-
const decoratedAgent = typeof options.decorate === 'function'
|
|
454
|
-
? {
|
|
455
|
-
...agent,
|
|
456
|
-
...options.decorate(agent)
|
|
457
|
-
}
|
|
458
|
-
: agent;
|
|
459
|
-
|
|
460
|
-
agents.push(decoratedAgent);
|
|
427
|
+
});
|
|
461
428
|
} catch (err) {
|
|
462
429
|
console.warn(`[AgentsService] Failed to parse ${fullPath}:`, err.message);
|
|
463
430
|
}
|
|
@@ -566,7 +533,6 @@ class AgentsService {
|
|
|
566
533
|
const config = PLATFORM_CONFIG[this.platform];
|
|
567
534
|
|
|
568
535
|
this.userAgentsDir = config.userAgentsDir;
|
|
569
|
-
this.storageDir = config.storageDir;
|
|
570
536
|
if (this.platform === 'opencode') {
|
|
571
537
|
const legacyUserDir = config.legacyUserAgentsDir;
|
|
572
538
|
if (legacyUserDir && fs.existsSync(legacyUserDir) && !fs.existsSync(this.userAgentsDir)) {
|
|
@@ -577,9 +543,6 @@ class AgentsService {
|
|
|
577
543
|
this.projectAgentsDir = config.projectAgentsDir;
|
|
578
544
|
this.repoScanner = new AgentsRepoScanner(this.platform, this.userAgentsDir);
|
|
579
545
|
ensureDir(this.userAgentsDir);
|
|
580
|
-
if (this.platform !== 'codex') {
|
|
581
|
-
ensureDir(this.storageDir);
|
|
582
|
-
}
|
|
583
546
|
}
|
|
584
547
|
|
|
585
548
|
getProjectAgentsDir(projectPath) {
|
|
@@ -588,146 +551,36 @@ class AgentsService {
|
|
|
588
551
|
return this.projectAgentsDir(safeProjectPath);
|
|
589
552
|
}
|
|
590
553
|
|
|
591
|
-
getManagedAgentPath(fileName) {
|
|
592
|
-
assertSafeAgentFileName(fileName);
|
|
593
|
-
return path.join(this.storageDir, `${fileName}.md`);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
isInstalledAgent(fileName) {
|
|
597
|
-
assertSafeAgentFileName(fileName);
|
|
598
|
-
return fs.existsSync(path.join(this.userAgentsDir, `${fileName}.md`));
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
buildAgentContent({ fileName, name, description, tools, model, permissionMode, skills, systemPrompt }) {
|
|
602
|
-
const frontmatterData = {
|
|
603
|
-
name: name || fileName,
|
|
604
|
-
description: description || ''
|
|
605
|
-
};
|
|
606
|
-
if (tools) frontmatterData.tools = tools;
|
|
607
|
-
if (model) frontmatterData.model = model;
|
|
608
|
-
if (permissionMode) frontmatterData.permissionMode = permissionMode;
|
|
609
|
-
if (skills) frontmatterData.skills = skills;
|
|
610
|
-
|
|
611
|
-
return generateFrontmatter(frontmatterData, this.platform) + '\n\n' + (systemPrompt || '');
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
ensureManagedAgentCopy(fileName, sourcePath, options = {}) {
|
|
615
|
-
const overwrite = options.overwrite === true;
|
|
616
|
-
assertSafeAgentFileName(fileName);
|
|
617
|
-
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
618
|
-
return false;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const managedPath = this.getManagedAgentPath(fileName);
|
|
622
|
-
if (fs.existsSync(managedPath) && !overwrite) {
|
|
623
|
-
return false;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
ensureDir(path.dirname(managedPath));
|
|
627
|
-
fs.copyFileSync(sourcePath, managedPath);
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
mergeInstalledUserAgents(agents, options = {}) {
|
|
632
|
-
const syncManagedLocalAgents = options.syncManagedLocalAgents === true;
|
|
633
|
-
const installedAgents = scanAgentsDir(this.userAgentsDir, this.userAgentsDir, 'user', {
|
|
634
|
-
decorate: () => ({
|
|
635
|
-
installed: true,
|
|
636
|
-
isManagedLocal: false,
|
|
637
|
-
source: 'native-installed'
|
|
638
|
-
})
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
for (const installedAgent of installedAgents) {
|
|
642
|
-
const existing = agents.find(agent =>
|
|
643
|
-
agent.scope === 'user' &&
|
|
644
|
-
agent.fileName.toLowerCase() === installedAgent.fileName.toLowerCase()
|
|
645
|
-
);
|
|
646
|
-
const managedPath = this.getManagedAgentPath(installedAgent.fileName);
|
|
647
|
-
|
|
648
|
-
if (existing) {
|
|
649
|
-
if (syncManagedLocalAgents && fs.existsSync(managedPath)) {
|
|
650
|
-
this.ensureManagedAgentCopy(installedAgent.fileName, installedAgent.fullPath, { overwrite: true });
|
|
651
|
-
}
|
|
652
|
-
existing.installed = true;
|
|
653
|
-
if (!existing.description && installedAgent.description) existing.description = installedAgent.description;
|
|
654
|
-
if (!existing.systemPrompt && installedAgent.systemPrompt) existing.systemPrompt = installedAgent.systemPrompt;
|
|
655
|
-
if (!existing.fullContent && installedAgent.fullContent) existing.fullContent = installedAgent.fullContent;
|
|
656
|
-
continue;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
this.ensureManagedAgentCopy(installedAgent.fileName, installedAgent.fullPath, {
|
|
660
|
-
overwrite: syncManagedLocalAgents
|
|
661
|
-
});
|
|
662
|
-
agents.push(installedAgent);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
mergeLocalUserAgents(agents) {
|
|
667
|
-
if (!fs.existsSync(this.storageDir)) return;
|
|
668
|
-
|
|
669
|
-
const localAgents = scanAgentsDir(this.storageDir, this.storageDir, 'user', {
|
|
670
|
-
decorate: (agent) => ({
|
|
671
|
-
installed: this.isInstalledAgent(agent.fileName),
|
|
672
|
-
isManagedLocal: true,
|
|
673
|
-
source: 'local'
|
|
674
|
-
})
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
for (const localAgent of localAgents) {
|
|
678
|
-
const existing = agents.find(agent =>
|
|
679
|
-
agent.scope === 'user' &&
|
|
680
|
-
agent.fileName.toLowerCase() === localAgent.fileName.toLowerCase()
|
|
681
|
-
);
|
|
682
|
-
|
|
683
|
-
if (existing) {
|
|
684
|
-
existing.isManagedLocal = true;
|
|
685
|
-
existing.installed = this.isInstalledAgent(existing.fileName);
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
agents.push(localAgent);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
554
|
/**
|
|
694
555
|
* 获取所有代理列表
|
|
695
556
|
* @param {string} projectPath - 项目路径(可选,用于获取项目级代理)
|
|
696
557
|
*/
|
|
697
|
-
listAgents(projectPath = null
|
|
558
|
+
listAgents(projectPath = null) {
|
|
698
559
|
if (this.platform === 'codex') {
|
|
699
560
|
return this.listCodexAgents();
|
|
700
561
|
}
|
|
701
562
|
|
|
702
563
|
const agents = [];
|
|
703
|
-
const syncManagedLocalAgents = options.syncManagedLocalAgents === true;
|
|
704
564
|
|
|
705
|
-
|
|
706
|
-
this.
|
|
565
|
+
// 获取用户级代理
|
|
566
|
+
const userAgents = scanAgentsDir(this.userAgentsDir, this.userAgentsDir, 'user');
|
|
567
|
+
agents.push(...userAgents);
|
|
707
568
|
|
|
708
569
|
// 获取项目级代理(如果提供了项目路径)
|
|
709
570
|
if (projectPath) {
|
|
710
571
|
const projectAgentsDir = this.getProjectAgentsDir(projectPath);
|
|
711
|
-
const projectAgents = scanAgentsDir(projectAgentsDir, projectAgentsDir, 'project'
|
|
712
|
-
decorate: () => ({
|
|
713
|
-
installed: true,
|
|
714
|
-
isManagedLocal: false,
|
|
715
|
-
source: 'native-installed'
|
|
716
|
-
})
|
|
717
|
-
});
|
|
572
|
+
const projectAgents = scanAgentsDir(projectAgentsDir, projectAgentsDir, 'project');
|
|
718
573
|
agents.push(...projectAgents);
|
|
719
574
|
}
|
|
720
575
|
|
|
721
576
|
// 按名称排序
|
|
722
577
|
agents.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
|
723
|
-
const userCount = agents.filter(agent => agent.scope === 'user').length;
|
|
724
|
-
const projectCount = agents.length - userCount;
|
|
725
578
|
|
|
726
579
|
return {
|
|
727
580
|
agents,
|
|
728
581
|
total: agents.length,
|
|
729
|
-
userCount,
|
|
730
|
-
projectCount
|
|
582
|
+
userCount: userAgents.length,
|
|
583
|
+
projectCount: agents.length - userAgents.length
|
|
731
584
|
};
|
|
732
585
|
}
|
|
733
586
|
|
|
@@ -747,9 +600,7 @@ class AgentsService {
|
|
|
747
600
|
}
|
|
748
601
|
|
|
749
602
|
// 获取本地代理
|
|
750
|
-
const { agents: localAgents, userCount, projectCount } = this.listAgents(projectPath
|
|
751
|
-
syncManagedLocalAgents: forceRefresh
|
|
752
|
-
});
|
|
603
|
+
const { agents: localAgents, userCount, projectCount } = this.listAgents(projectPath);
|
|
753
604
|
|
|
754
605
|
// 获取远程代理
|
|
755
606
|
let remoteAgents = [];
|
|
@@ -801,26 +652,20 @@ class AgentsService {
|
|
|
801
652
|
: this.getProjectAgentsDir(projectPath);
|
|
802
653
|
|
|
803
654
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
804
|
-
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
805
|
-
const activePath = fs.existsSync(filePath)
|
|
806
|
-
? filePath
|
|
807
|
-
: (managedPath && fs.existsSync(managedPath) ? managedPath : '');
|
|
808
655
|
|
|
809
|
-
if (!
|
|
656
|
+
if (!fs.existsSync(filePath)) {
|
|
810
657
|
return null;
|
|
811
658
|
}
|
|
812
659
|
|
|
813
|
-
const content = fs.readFileSync(
|
|
660
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
814
661
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
815
|
-
const installed = fs.existsSync(filePath);
|
|
816
|
-
const isManagedLocal = scope === 'user' && !!managedPath && fs.existsSync(managedPath);
|
|
817
662
|
|
|
818
663
|
return {
|
|
819
664
|
name: frontmatter.name || fileName,
|
|
820
665
|
fileName,
|
|
821
666
|
scope,
|
|
822
667
|
path: `${fileName}.md`,
|
|
823
|
-
fullPath:
|
|
668
|
+
fullPath: filePath,
|
|
824
669
|
description: frontmatter.description || '',
|
|
825
670
|
tools: frontmatter.tools || '',
|
|
826
671
|
model: frontmatter.model || '',
|
|
@@ -828,10 +673,7 @@ class AgentsService {
|
|
|
828
673
|
skills: frontmatter.skills || '',
|
|
829
674
|
systemPrompt: body,
|
|
830
675
|
fullContent: content,
|
|
831
|
-
|
|
832
|
-
isManagedLocal,
|
|
833
|
-
source: installed ? 'native-installed' : 'local',
|
|
834
|
-
updatedAt: fs.statSync(activePath).mtime.getTime()
|
|
676
|
+
updatedAt: fs.statSync(filePath).mtime.getTime()
|
|
835
677
|
};
|
|
836
678
|
}
|
|
837
679
|
|
|
@@ -860,29 +702,22 @@ class AgentsService {
|
|
|
860
702
|
ensureDir(baseDir);
|
|
861
703
|
|
|
862
704
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
863
|
-
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
864
705
|
|
|
865
706
|
// 检查是否已存在
|
|
866
|
-
if (fs.existsSync(filePath)
|
|
707
|
+
if (fs.existsSync(filePath)) {
|
|
867
708
|
throw new Error(`代理 "${fileName}" 已存在`);
|
|
868
709
|
}
|
|
869
710
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
systemPrompt
|
|
879
|
-
});
|
|
711
|
+
// 生成文件内容
|
|
712
|
+
const frontmatterData = { name: (name || fileName), description };
|
|
713
|
+
if (tools) frontmatterData.tools = tools;
|
|
714
|
+
if (model) frontmatterData.model = model;
|
|
715
|
+
if (permissionMode) frontmatterData.permissionMode = permissionMode;
|
|
716
|
+
if (skills) frontmatterData.skills = skills;
|
|
717
|
+
|
|
718
|
+
const content = generateFrontmatter(frontmatterData, this.platform) + '\n\n' + (systemPrompt || '');
|
|
880
719
|
|
|
881
720
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
882
|
-
if (managedPath) {
|
|
883
|
-
ensureDir(path.dirname(managedPath));
|
|
884
|
-
fs.writeFileSync(managedPath, content, 'utf-8');
|
|
885
|
-
}
|
|
886
721
|
|
|
887
722
|
return this.getAgent(fileName, scope, projectPath);
|
|
888
723
|
}
|
|
@@ -902,31 +737,24 @@ class AgentsService {
|
|
|
902
737
|
: this.getProjectAgentsDir(projectPath);
|
|
903
738
|
|
|
904
739
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
905
|
-
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
906
|
-
const hasManagedCopy = managedPath && fs.existsSync(managedPath);
|
|
907
740
|
|
|
908
|
-
if (!fs.existsSync(filePath)
|
|
741
|
+
if (!fs.existsSync(filePath)) {
|
|
909
742
|
throw new Error(`代理 "${fileName}" 不存在`);
|
|
910
743
|
}
|
|
911
744
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
name,
|
|
915
|
-
description
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
});
|
|
745
|
+
// 生成文件内容
|
|
746
|
+
const frontmatterData = {
|
|
747
|
+
name: name || fileName,
|
|
748
|
+
description: description || ''
|
|
749
|
+
};
|
|
750
|
+
if (tools) frontmatterData.tools = tools;
|
|
751
|
+
if (model) frontmatterData.model = model;
|
|
752
|
+
if (permissionMode) frontmatterData.permissionMode = permissionMode;
|
|
753
|
+
if (skills) frontmatterData.skills = skills;
|
|
922
754
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (managedPath) {
|
|
927
|
-
ensureDir(path.dirname(managedPath));
|
|
928
|
-
fs.writeFileSync(managedPath, content, 'utf-8');
|
|
929
|
-
}
|
|
755
|
+
const content = generateFrontmatter(frontmatterData, this.platform) + '\n\n' + (systemPrompt || '');
|
|
756
|
+
|
|
757
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
930
758
|
|
|
931
759
|
return this.getAgent(fileName, scope, projectPath);
|
|
932
760
|
}
|
|
@@ -946,23 +774,13 @@ class AgentsService {
|
|
|
946
774
|
: this.getProjectAgentsDir(projectPath);
|
|
947
775
|
|
|
948
776
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
949
|
-
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
950
|
-
let removed = false;
|
|
951
777
|
|
|
952
|
-
if (fs.existsSync(filePath)) {
|
|
953
|
-
fs.unlinkSync(filePath);
|
|
954
|
-
removed = true;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (managedPath && fs.existsSync(managedPath)) {
|
|
958
|
-
fs.unlinkSync(managedPath);
|
|
959
|
-
removed = true;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
if (!removed) {
|
|
778
|
+
if (!fs.existsSync(filePath)) {
|
|
963
779
|
return { success: false, message: '代理不存在' };
|
|
964
780
|
}
|
|
965
781
|
|
|
782
|
+
fs.unlinkSync(filePath);
|
|
783
|
+
|
|
966
784
|
return { success: true, message: '代理已删除' };
|
|
967
785
|
}
|
|
968
786
|
|
|
@@ -1042,37 +860,12 @@ class AgentsService {
|
|
|
1042
860
|
return this.repoScanner.installAgent(agent);
|
|
1043
861
|
}
|
|
1044
862
|
|
|
1045
|
-
installLocalAgent(fileName) {
|
|
1046
|
-
assertSafeAgentFileName(fileName);
|
|
1047
|
-
const managedPath = this.getManagedAgentPath(fileName);
|
|
1048
|
-
const targetPath = path.join(this.userAgentsDir, `${fileName}.md`);
|
|
1049
|
-
|
|
1050
|
-
if (!fs.existsSync(managedPath)) {
|
|
1051
|
-
throw new Error(`本地代理 "${fileName}" 不存在`);
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
if (fs.existsSync(targetPath)) {
|
|
1055
|
-
return { success: true, message: 'Already installed' };
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
ensureDir(path.dirname(targetPath));
|
|
1059
|
-
fs.copyFileSync(managedPath, targetPath);
|
|
1060
|
-
return { success: true, message: 'Installed successfully' };
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
863
|
/**
|
|
1064
864
|
* 卸载代理
|
|
1065
865
|
*/
|
|
1066
866
|
uninstallAgent(fileName) {
|
|
1067
867
|
assertSafeAgentFileName(fileName);
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
if (fs.existsSync(targetPath)) {
|
|
1071
|
-
fs.unlinkSync(targetPath);
|
|
1072
|
-
return { success: true, message: 'Uninstalled successfully' };
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
return { success: true, message: 'Not installed' };
|
|
868
|
+
return this.repoScanner.uninstall(`${fileName}.md`);
|
|
1076
869
|
}
|
|
1077
870
|
|
|
1078
871
|
listCodexAgents() {
|
|
@@ -1193,7 +986,7 @@ class AgentsService {
|
|
|
1193
986
|
} else if (normalizedMode === 'custom') {
|
|
1194
987
|
const safeConfigFile = assertSafeCodexConfigPath(configFile);
|
|
1195
988
|
const resolvedPath = resolveCodexConfigPath(safeConfigFile);
|
|
1196
|
-
ensureDir(
|
|
989
|
+
ensureDir(path.dirname(resolvedPath));
|
|
1197
990
|
const content = resolveCodexConfigContent({ configContent, model });
|
|
1198
991
|
writeFileAtomic(resolvedPath, content);
|
|
1199
992
|
agentConfig.config_file = safeConfigFile;
|
|
@@ -1239,7 +1032,7 @@ class AgentsService {
|
|
|
1239
1032
|
const parsedConfigFile = readCodexAgentConfigFile(resolvedConfigFilePath);
|
|
1240
1033
|
const configFileData = isPlainObject(parsedConfigFile?.data) ? parsedConfigFile.data : {};
|
|
1241
1034
|
configFileData.model = trimmedModel;
|
|
1242
|
-
ensureDir(
|
|
1035
|
+
ensureDir(path.dirname(resolvedConfigFilePath));
|
|
1243
1036
|
writeFileAtomic(resolvedConfigFilePath, tomlStringify(configFileData));
|
|
1244
1037
|
agentConfig.config_file = configFilePath;
|
|
1245
1038
|
} else if (isExistingManagedConfig) {
|
|
@@ -1265,7 +1058,7 @@ class AgentsService {
|
|
|
1265
1058
|
model,
|
|
1266
1059
|
fallbackContent: existingManagedContent
|
|
1267
1060
|
});
|
|
1268
|
-
ensureDir(
|
|
1061
|
+
ensureDir(path.dirname(resolvedConfigFilePath));
|
|
1269
1062
|
writeFileAtomic(resolvedConfigFilePath, content);
|
|
1270
1063
|
agentConfig.config_file = configFilePath;
|
|
1271
1064
|
if (resolvedExistingConfigFile &&
|
|
@@ -1294,7 +1087,7 @@ class AgentsService {
|
|
|
1294
1087
|
model,
|
|
1295
1088
|
fallbackContent: existingContent
|
|
1296
1089
|
});
|
|
1297
|
-
ensureDir(
|
|
1090
|
+
ensureDir(path.dirname(resolvedTargetConfigPath));
|
|
1298
1091
|
writeFileAtomic(resolvedTargetConfigPath, content);
|
|
1299
1092
|
agentConfig.config_file = targetConfigPath;
|
|
1300
1093
|
if (resolvedExistingConfigFile &&
|
|
@@ -11,6 +11,7 @@ const fs = require('fs');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const crypto = require('crypto');
|
|
13
13
|
const { normalizeGatewaySourceType, normalizeNumber } = require('./proxy-utils');
|
|
14
|
+
const { resolveChannelWebsiteUrl } = require('../../../config/channel-preset-websites');
|
|
14
15
|
|
|
15
16
|
class BaseChannelService {
|
|
16
17
|
/**
|
|
@@ -225,6 +226,7 @@ class BaseChannelService {
|
|
|
225
226
|
normalized.gatewaySourceType,
|
|
226
227
|
this.defaultGatewaySource
|
|
227
228
|
);
|
|
229
|
+
normalized.websiteUrl = resolveChannelWebsiteUrl(this.platform, normalized);
|
|
228
230
|
return normalized;
|
|
229
231
|
}
|
|
230
232
|
|
|
@@ -248,4 +250,4 @@ class BaseChannelService {
|
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
|
|
251
|
-
module.exports = BaseChannelService;
|
|
253
|
+
module.exports = BaseChannelService;
|
|
@@ -301,6 +301,10 @@ function updateChannel(id, updates) {
|
|
|
301
301
|
return service.updateChannel(id, updates);
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
function markChannelAsRecentlyUsed(id) {
|
|
305
|
+
return service.updateChannel(id, {});
|
|
306
|
+
}
|
|
307
|
+
|
|
304
308
|
function deleteChannel(id) {
|
|
305
309
|
return service.deleteChannel(id);
|
|
306
310
|
}
|
|
@@ -323,6 +327,7 @@ module.exports = {
|
|
|
323
327
|
getCurrentSettings,
|
|
324
328
|
createChannel,
|
|
325
329
|
updateChannel,
|
|
330
|
+
markChannelAsRecentlyUsed,
|
|
326
331
|
deleteChannel,
|
|
327
332
|
applyChannelToSettings,
|
|
328
333
|
getBestChannelForRestore,
|
|
@@ -261,6 +261,7 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi, extraConfig
|
|
|
261
261
|
});
|
|
262
262
|
}
|
|
263
263
|
function updateChannel(id, updates) { return service.updateChannel(id, updates); }
|
|
264
|
+
function markChannelAsRecentlyUsed(id) { return service.updateChannel(id, {}); }
|
|
264
265
|
function deleteChannel(id) { return service.deleteChannel(id); }
|
|
265
266
|
function saveChannelOrder(order) { return service.saveChannelOrder(order); }
|
|
266
267
|
function applyChannelToSettings(id) { return service.applyChannelToSettings(id); }
|
|
@@ -281,6 +282,7 @@ module.exports = {
|
|
|
281
282
|
getChannels,
|
|
282
283
|
createChannel,
|
|
283
284
|
updateChannel,
|
|
285
|
+
markChannelAsRecentlyUsed,
|
|
284
286
|
deleteChannel,
|
|
285
287
|
getEnabledChannels,
|
|
286
288
|
saveChannelOrder,
|