coding-tool-x 3.5.7 → 3.5.9
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-C6DEmD3D.js → Analytics-C5W3axXs.js} +2 -2
- package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
- package/dist/web/assets/{ConfigTemplates-Cf_iTpC4.js → ConfigTemplates-DzyVFDx9.js} +1 -1
- package/dist/web/assets/{Home-BtBmYLJ1.js → Home-C9TQNB6f.js} +1 -1
- package/dist/web/assets/Home-qzk118Of.css +1 -0
- package/dist/web/assets/{PluginManager-DEk8vSw5.js → PluginManager-9B_brLWT.js} +1 -1
- package/dist/web/assets/ProjectList-Bjt6mrsV.js +1 -0
- package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
- package/dist/web/assets/SessionList-BsHPgmUR.css +1 -0
- package/dist/web/assets/SessionList-DcBH13uA.js +1 -0
- package/dist/web/assets/{SkillManager-DcZOiiSf.js → SkillManager-vST8DRRg.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-BHqI8aGV.js → WorkspaceManager-ov1KgRXR.js} +1 -1
- package/dist/web/assets/icons-CEq2hYB-.js +1 -0
- package/dist/web/assets/index-Dih_bOsv.css +1 -0
- package/dist/web/assets/index-Duc7QP4e.js +2 -0
- package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
- package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
- package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
- package/dist/web/index.html +7 -7
- package/package.json +1 -1
- package/src/commands/export-config.js +6 -6
- package/src/config/default.js +2 -6
- package/src/config/loader.js +2 -2
- package/src/config/paths.js +160 -33
- package/src/server/api/agents.js +52 -2
- package/src/server/api/codex-sessions.js +4 -2
- package/src/server/api/commands.js +38 -2
- package/src/server/api/opencode-sessions.js +4 -2
- package/src/server/api/plugins.js +104 -1
- package/src/server/api/sessions.js +9 -7
- package/src/server/services/agents-service.js +269 -62
- package/src/server/services/commands-service.js +281 -81
- package/src/server/services/config-export-service.js +7 -7
- package/src/server/services/config-registry-service.js +4 -5
- package/src/server/services/config-sync-manager.js +61 -41
- package/src/server/services/config-sync-service.js +3 -3
- package/src/server/services/gemini-channels.js +5 -5
- package/src/server/services/gemini-config.js +3 -4
- package/src/server/services/gemini-sessions.js +23 -20
- package/src/server/services/gemini-settings-manager.js +2 -3
- package/src/server/services/mcp-service.js +9 -14
- package/src/server/services/native-oauth-adapters.js +3 -3
- package/src/server/services/notification-hooks.js +3 -3
- package/src/server/services/opencode-sessions.js +16 -6
- package/src/server/services/opencode-settings-manager.js +3 -3
- package/src/server/services/plugins-service.js +499 -23
- package/src/server/services/prompts-service.js +5 -9
- package/src/server/services/session-launch-command.js +1 -24
- package/src/server/services/sessions.js +91 -40
- package/src/server/services/skill-service.js +155 -18
- package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
- package/dist/web/assets/ProjectList-BMVhA_Kh.js +0 -1
- package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
- package/dist/web/assets/SessionList-B5ioAXxg.js +0 -1
- package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
- package/dist/web/assets/icons-CQuif85v.js +0 -1
- package/dist/web/assets/index-CtByKdkA.js +0 -2
- package/dist/web/assets/index-VGAxnLqi.css +0 -1
- package/dist/web/assets/vue-vendor-aWwwFAao.js +0 -45
|
@@ -7,32 +7,42 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
10
|
const toml = require('toml');
|
|
12
11
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
13
12
|
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
14
|
-
const {
|
|
15
|
-
|
|
13
|
+
const {
|
|
14
|
+
NATIVE_PATHS,
|
|
15
|
+
PATHS,
|
|
16
|
+
HOME_DIR,
|
|
17
|
+
isWindowsAbsolutePath,
|
|
18
|
+
isNativeAbsolutePath,
|
|
19
|
+
joinNativeBasePath,
|
|
20
|
+
resolveNativeBasePath,
|
|
21
|
+
getNativePathDir
|
|
22
|
+
} = require('../../config/paths');
|
|
16
23
|
|
|
17
24
|
// 默认仓库源
|
|
18
25
|
const DEFAULT_REPOS = [];
|
|
19
26
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'opencode'];
|
|
20
|
-
const
|
|
27
|
+
const OPENCODE_AGENTS_DIR = NATIVE_PATHS.opencode.agents;
|
|
28
|
+
const OPENCODE_LEGACY_AGENTS_DIR = NATIVE_PATHS.opencode.agentsLegacy;
|
|
29
|
+
const CODEX_DIR = NATIVE_PATHS.codex.dir;
|
|
21
30
|
const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const CLAUDE_AGENTS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'agents');
|
|
31
|
+
const CODEX_AGENTS_DIR = NATIVE_PATHS.codex.agents;
|
|
32
|
+
const CLAUDE_AGENTS_DIR = NATIVE_PATHS.claude.agents;
|
|
25
33
|
const CODEX_CONFIG_MODES = new Set(['none', 'managed', 'custom']);
|
|
26
34
|
|
|
27
35
|
const PLATFORM_CONFIG = {
|
|
28
36
|
claude: {
|
|
29
37
|
userAgentsDir: CLAUDE_AGENTS_DIR,
|
|
38
|
+
storageDir: PATHS.localAgents.claude,
|
|
30
39
|
projectAgentsDir: (projectPath) => path.join(projectPath, '.claude', 'agents'),
|
|
31
40
|
repoType: 'agents'
|
|
32
41
|
},
|
|
33
42
|
opencode: {
|
|
34
|
-
userAgentsDir:
|
|
35
|
-
|
|
43
|
+
userAgentsDir: OPENCODE_AGENTS_DIR,
|
|
44
|
+
storageDir: PATHS.localAgents.opencode,
|
|
45
|
+
legacyUserAgentsDir: OPENCODE_LEGACY_AGENTS_DIR,
|
|
36
46
|
projectAgentsDir: (projectPath) => {
|
|
37
47
|
const modern = path.join(projectPath, '.opencode', 'agents');
|
|
38
48
|
const legacy = path.join(projectPath, '.opencode', 'agent');
|
|
@@ -45,6 +55,7 @@ const PLATFORM_CONFIG = {
|
|
|
45
55
|
},
|
|
46
56
|
codex: {
|
|
47
57
|
userAgentsDir: CODEX_AGENTS_DIR,
|
|
58
|
+
storageDir: PATHS.localAgents.codex,
|
|
48
59
|
projectAgentsDir: () => null,
|
|
49
60
|
repoType: 'agents'
|
|
50
61
|
}
|
|
@@ -149,7 +160,7 @@ function readCodexTomlConfig() {
|
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
function writeCodexTomlConfig(config) {
|
|
152
|
-
ensureDir(
|
|
163
|
+
ensureDir(getNativePathDir(CODEX_CONFIG_PATH));
|
|
153
164
|
writeFileAtomic(CODEX_CONFIG_PATH, tomlStringify(config));
|
|
154
165
|
}
|
|
155
166
|
|
|
@@ -158,7 +169,7 @@ function isPlainObject(value) {
|
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
function getCodexManagedAgentConfigPath(fileName) {
|
|
161
|
-
return
|
|
172
|
+
return joinNativeBasePath(CODEX_AGENTS_DIR, `${fileName}.toml`);
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
function normalizeCodexConfigPath(configPath) {
|
|
@@ -187,7 +198,7 @@ function assertSafeCodexConfigPath(configPath) {
|
|
|
187
198
|
return `~/${relative}`;
|
|
188
199
|
}
|
|
189
200
|
|
|
190
|
-
if (
|
|
201
|
+
if (isNativeAbsolutePath(normalized)) {
|
|
191
202
|
return normalized;
|
|
192
203
|
}
|
|
193
204
|
|
|
@@ -209,21 +220,35 @@ function resolveCodexConfigPath(configPath) {
|
|
|
209
220
|
if (!normalized) return '';
|
|
210
221
|
|
|
211
222
|
if (normalized.startsWith('~/')) {
|
|
212
|
-
return
|
|
223
|
+
return joinNativeBasePath(HOME_DIR, normalized.slice(2));
|
|
213
224
|
}
|
|
214
225
|
|
|
215
|
-
if (
|
|
226
|
+
if (isNativeAbsolutePath(normalized)) {
|
|
216
227
|
return normalized;
|
|
217
228
|
}
|
|
218
229
|
|
|
219
|
-
return
|
|
230
|
+
return resolveNativeBasePath(CODEX_DIR, normalized);
|
|
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}/`);
|
|
220
245
|
}
|
|
221
246
|
|
|
222
247
|
function isManagedCodexConfigPath(configPath) {
|
|
223
248
|
const resolved = resolveCodexConfigPath(configPath);
|
|
224
249
|
if (!resolved) return false;
|
|
225
|
-
const managedRoot =
|
|
226
|
-
return
|
|
250
|
+
const managedRoot = resolveNativeBasePath(CODEX_AGENTS_DIR);
|
|
251
|
+
return isPathInsideNativeDir(resolved, managedRoot);
|
|
227
252
|
}
|
|
228
253
|
|
|
229
254
|
function getManagedCodexConfigResolvedPath(configPath) {
|
|
@@ -387,7 +412,7 @@ function generateFrontmatter(data, platform = 'claude') {
|
|
|
387
412
|
/**
|
|
388
413
|
* 扫描目录获取代理文件(agents 约定为扁平目录)
|
|
389
414
|
*/
|
|
390
|
-
function scanAgentsDir(dir, basePath, scope) {
|
|
415
|
+
function scanAgentsDir(dir, basePath, scope, options = {}) {
|
|
391
416
|
const agents = [];
|
|
392
417
|
|
|
393
418
|
if (!fs.existsSync(dir)) {
|
|
@@ -410,7 +435,7 @@ function scanAgentsDir(dir, basePath, scope) {
|
|
|
410
435
|
const relativePath = path.relative(basePath, fullPath);
|
|
411
436
|
const fileName = entry.name.replace(/\.md$/, '');
|
|
412
437
|
|
|
413
|
-
|
|
438
|
+
const agent = {
|
|
414
439
|
name: frontmatter.name || fileName,
|
|
415
440
|
fileName,
|
|
416
441
|
scope,
|
|
@@ -424,7 +449,15 @@ function scanAgentsDir(dir, basePath, scope) {
|
|
|
424
449
|
systemPrompt: body,
|
|
425
450
|
fullContent: content,
|
|
426
451
|
updatedAt: fs.statSync(fullPath).mtime.getTime()
|
|
427
|
-
}
|
|
452
|
+
};
|
|
453
|
+
const decoratedAgent = typeof options.decorate === 'function'
|
|
454
|
+
? {
|
|
455
|
+
...agent,
|
|
456
|
+
...options.decorate(agent)
|
|
457
|
+
}
|
|
458
|
+
: agent;
|
|
459
|
+
|
|
460
|
+
agents.push(decoratedAgent);
|
|
428
461
|
} catch (err) {
|
|
429
462
|
console.warn(`[AgentsService] Failed to parse ${fullPath}:`, err.message);
|
|
430
463
|
}
|
|
@@ -533,6 +566,7 @@ class AgentsService {
|
|
|
533
566
|
const config = PLATFORM_CONFIG[this.platform];
|
|
534
567
|
|
|
535
568
|
this.userAgentsDir = config.userAgentsDir;
|
|
569
|
+
this.storageDir = config.storageDir;
|
|
536
570
|
if (this.platform === 'opencode') {
|
|
537
571
|
const legacyUserDir = config.legacyUserAgentsDir;
|
|
538
572
|
if (legacyUserDir && fs.existsSync(legacyUserDir) && !fs.existsSync(this.userAgentsDir)) {
|
|
@@ -543,6 +577,9 @@ class AgentsService {
|
|
|
543
577
|
this.projectAgentsDir = config.projectAgentsDir;
|
|
544
578
|
this.repoScanner = new AgentsRepoScanner(this.platform, this.userAgentsDir);
|
|
545
579
|
ensureDir(this.userAgentsDir);
|
|
580
|
+
if (this.platform !== 'codex') {
|
|
581
|
+
ensureDir(this.storageDir);
|
|
582
|
+
}
|
|
546
583
|
}
|
|
547
584
|
|
|
548
585
|
getProjectAgentsDir(projectPath) {
|
|
@@ -551,36 +588,146 @@ class AgentsService {
|
|
|
551
588
|
return this.projectAgentsDir(safeProjectPath);
|
|
552
589
|
}
|
|
553
590
|
|
|
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
|
+
|
|
554
693
|
/**
|
|
555
694
|
* 获取所有代理列表
|
|
556
695
|
* @param {string} projectPath - 项目路径(可选,用于获取项目级代理)
|
|
557
696
|
*/
|
|
558
|
-
listAgents(projectPath = null) {
|
|
697
|
+
listAgents(projectPath = null, options = {}) {
|
|
559
698
|
if (this.platform === 'codex') {
|
|
560
699
|
return this.listCodexAgents();
|
|
561
700
|
}
|
|
562
701
|
|
|
563
702
|
const agents = [];
|
|
703
|
+
const syncManagedLocalAgents = options.syncManagedLocalAgents === true;
|
|
564
704
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
agents.push(...userAgents);
|
|
705
|
+
this.mergeInstalledUserAgents(agents, { syncManagedLocalAgents });
|
|
706
|
+
this.mergeLocalUserAgents(agents);
|
|
568
707
|
|
|
569
708
|
// 获取项目级代理(如果提供了项目路径)
|
|
570
709
|
if (projectPath) {
|
|
571
710
|
const projectAgentsDir = this.getProjectAgentsDir(projectPath);
|
|
572
|
-
const projectAgents = scanAgentsDir(projectAgentsDir, projectAgentsDir, 'project'
|
|
711
|
+
const projectAgents = scanAgentsDir(projectAgentsDir, projectAgentsDir, 'project', {
|
|
712
|
+
decorate: () => ({
|
|
713
|
+
installed: true,
|
|
714
|
+
isManagedLocal: false,
|
|
715
|
+
source: 'native-installed'
|
|
716
|
+
})
|
|
717
|
+
});
|
|
573
718
|
agents.push(...projectAgents);
|
|
574
719
|
}
|
|
575
720
|
|
|
576
721
|
// 按名称排序
|
|
577
722
|
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;
|
|
578
725
|
|
|
579
726
|
return {
|
|
580
727
|
agents,
|
|
581
728
|
total: agents.length,
|
|
582
|
-
userCount
|
|
583
|
-
projectCount
|
|
729
|
+
userCount,
|
|
730
|
+
projectCount
|
|
584
731
|
};
|
|
585
732
|
}
|
|
586
733
|
|
|
@@ -600,7 +747,9 @@ class AgentsService {
|
|
|
600
747
|
}
|
|
601
748
|
|
|
602
749
|
// 获取本地代理
|
|
603
|
-
const { agents: localAgents, userCount, projectCount } = this.listAgents(projectPath
|
|
750
|
+
const { agents: localAgents, userCount, projectCount } = this.listAgents(projectPath, {
|
|
751
|
+
syncManagedLocalAgents: forceRefresh
|
|
752
|
+
});
|
|
604
753
|
|
|
605
754
|
// 获取远程代理
|
|
606
755
|
let remoteAgents = [];
|
|
@@ -652,20 +801,26 @@ class AgentsService {
|
|
|
652
801
|
: this.getProjectAgentsDir(projectPath);
|
|
653
802
|
|
|
654
803
|
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 : '');
|
|
655
808
|
|
|
656
|
-
if (!
|
|
809
|
+
if (!activePath) {
|
|
657
810
|
return null;
|
|
658
811
|
}
|
|
659
812
|
|
|
660
|
-
const content = fs.readFileSync(
|
|
813
|
+
const content = fs.readFileSync(activePath, 'utf-8');
|
|
661
814
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
815
|
+
const installed = fs.existsSync(filePath);
|
|
816
|
+
const isManagedLocal = scope === 'user' && !!managedPath && fs.existsSync(managedPath);
|
|
662
817
|
|
|
663
818
|
return {
|
|
664
819
|
name: frontmatter.name || fileName,
|
|
665
820
|
fileName,
|
|
666
821
|
scope,
|
|
667
822
|
path: `${fileName}.md`,
|
|
668
|
-
fullPath:
|
|
823
|
+
fullPath: activePath,
|
|
669
824
|
description: frontmatter.description || '',
|
|
670
825
|
tools: frontmatter.tools || '',
|
|
671
826
|
model: frontmatter.model || '',
|
|
@@ -673,7 +828,10 @@ class AgentsService {
|
|
|
673
828
|
skills: frontmatter.skills || '',
|
|
674
829
|
systemPrompt: body,
|
|
675
830
|
fullContent: content,
|
|
676
|
-
|
|
831
|
+
installed,
|
|
832
|
+
isManagedLocal,
|
|
833
|
+
source: installed ? 'native-installed' : 'local',
|
|
834
|
+
updatedAt: fs.statSync(activePath).mtime.getTime()
|
|
677
835
|
};
|
|
678
836
|
}
|
|
679
837
|
|
|
@@ -702,22 +860,29 @@ class AgentsService {
|
|
|
702
860
|
ensureDir(baseDir);
|
|
703
861
|
|
|
704
862
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
863
|
+
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
705
864
|
|
|
706
865
|
// 检查是否已存在
|
|
707
|
-
if (fs.existsSync(filePath)) {
|
|
866
|
+
if (fs.existsSync(filePath) || (managedPath && fs.existsSync(managedPath))) {
|
|
708
867
|
throw new Error(`代理 "${fileName}" 已存在`);
|
|
709
868
|
}
|
|
710
869
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
870
|
+
const content = this.buildAgentContent({
|
|
871
|
+
fileName,
|
|
872
|
+
name,
|
|
873
|
+
description,
|
|
874
|
+
tools,
|
|
875
|
+
model,
|
|
876
|
+
permissionMode,
|
|
877
|
+
skills,
|
|
878
|
+
systemPrompt
|
|
879
|
+
});
|
|
719
880
|
|
|
720
881
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
882
|
+
if (managedPath) {
|
|
883
|
+
ensureDir(path.dirname(managedPath));
|
|
884
|
+
fs.writeFileSync(managedPath, content, 'utf-8');
|
|
885
|
+
}
|
|
721
886
|
|
|
722
887
|
return this.getAgent(fileName, scope, projectPath);
|
|
723
888
|
}
|
|
@@ -737,24 +902,31 @@ class AgentsService {
|
|
|
737
902
|
: this.getProjectAgentsDir(projectPath);
|
|
738
903
|
|
|
739
904
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
905
|
+
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
906
|
+
const hasManagedCopy = managedPath && fs.existsSync(managedPath);
|
|
740
907
|
|
|
741
|
-
if (!fs.existsSync(filePath)) {
|
|
908
|
+
if (!fs.existsSync(filePath) && !hasManagedCopy) {
|
|
742
909
|
throw new Error(`代理 "${fileName}" 不存在`);
|
|
743
910
|
}
|
|
744
911
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
name
|
|
748
|
-
description
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const content = generateFrontmatter(frontmatterData, this.platform) + '\n\n' + (systemPrompt || '');
|
|
912
|
+
const content = this.buildAgentContent({
|
|
913
|
+
fileName,
|
|
914
|
+
name,
|
|
915
|
+
description,
|
|
916
|
+
tools,
|
|
917
|
+
model,
|
|
918
|
+
permissionMode,
|
|
919
|
+
skills,
|
|
920
|
+
systemPrompt
|
|
921
|
+
});
|
|
756
922
|
|
|
757
|
-
fs.
|
|
923
|
+
if (fs.existsSync(filePath)) {
|
|
924
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
925
|
+
}
|
|
926
|
+
if (managedPath) {
|
|
927
|
+
ensureDir(path.dirname(managedPath));
|
|
928
|
+
fs.writeFileSync(managedPath, content, 'utf-8');
|
|
929
|
+
}
|
|
758
930
|
|
|
759
931
|
return this.getAgent(fileName, scope, projectPath);
|
|
760
932
|
}
|
|
@@ -774,12 +946,22 @@ class AgentsService {
|
|
|
774
946
|
: this.getProjectAgentsDir(projectPath);
|
|
775
947
|
|
|
776
948
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
949
|
+
const managedPath = scope === 'user' ? this.getManagedAgentPath(fileName) : '';
|
|
950
|
+
let removed = false;
|
|
777
951
|
|
|
778
|
-
if (
|
|
779
|
-
|
|
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;
|
|
780
960
|
}
|
|
781
961
|
|
|
782
|
-
|
|
962
|
+
if (!removed) {
|
|
963
|
+
return { success: false, message: '代理不存在' };
|
|
964
|
+
}
|
|
783
965
|
|
|
784
966
|
return { success: true, message: '代理已删除' };
|
|
785
967
|
}
|
|
@@ -860,12 +1042,37 @@ class AgentsService {
|
|
|
860
1042
|
return this.repoScanner.installAgent(agent);
|
|
861
1043
|
}
|
|
862
1044
|
|
|
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
|
+
|
|
863
1063
|
/**
|
|
864
1064
|
* 卸载代理
|
|
865
1065
|
*/
|
|
866
1066
|
uninstallAgent(fileName) {
|
|
867
1067
|
assertSafeAgentFileName(fileName);
|
|
868
|
-
|
|
1068
|
+
const targetPath = path.join(this.userAgentsDir, `${fileName}.md`);
|
|
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' };
|
|
869
1076
|
}
|
|
870
1077
|
|
|
871
1078
|
listCodexAgents() {
|
|
@@ -986,7 +1193,7 @@ class AgentsService {
|
|
|
986
1193
|
} else if (normalizedMode === 'custom') {
|
|
987
1194
|
const safeConfigFile = assertSafeCodexConfigPath(configFile);
|
|
988
1195
|
const resolvedPath = resolveCodexConfigPath(safeConfigFile);
|
|
989
|
-
ensureDir(
|
|
1196
|
+
ensureDir(getNativePathDir(resolvedPath));
|
|
990
1197
|
const content = resolveCodexConfigContent({ configContent, model });
|
|
991
1198
|
writeFileAtomic(resolvedPath, content);
|
|
992
1199
|
agentConfig.config_file = safeConfigFile;
|
|
@@ -1032,7 +1239,7 @@ class AgentsService {
|
|
|
1032
1239
|
const parsedConfigFile = readCodexAgentConfigFile(resolvedConfigFilePath);
|
|
1033
1240
|
const configFileData = isPlainObject(parsedConfigFile?.data) ? parsedConfigFile.data : {};
|
|
1034
1241
|
configFileData.model = trimmedModel;
|
|
1035
|
-
ensureDir(
|
|
1242
|
+
ensureDir(getNativePathDir(resolvedConfigFilePath));
|
|
1036
1243
|
writeFileAtomic(resolvedConfigFilePath, tomlStringify(configFileData));
|
|
1037
1244
|
agentConfig.config_file = configFilePath;
|
|
1038
1245
|
} else if (isExistingManagedConfig) {
|
|
@@ -1058,7 +1265,7 @@ class AgentsService {
|
|
|
1058
1265
|
model,
|
|
1059
1266
|
fallbackContent: existingManagedContent
|
|
1060
1267
|
});
|
|
1061
|
-
ensureDir(
|
|
1268
|
+
ensureDir(getNativePathDir(resolvedConfigFilePath));
|
|
1062
1269
|
writeFileAtomic(resolvedConfigFilePath, content);
|
|
1063
1270
|
agentConfig.config_file = configFilePath;
|
|
1064
1271
|
if (resolvedExistingConfigFile &&
|
|
@@ -1087,7 +1294,7 @@ class AgentsService {
|
|
|
1087
1294
|
model,
|
|
1088
1295
|
fallbackContent: existingContent
|
|
1089
1296
|
});
|
|
1090
|
-
ensureDir(
|
|
1297
|
+
ensureDir(getNativePathDir(resolvedTargetConfigPath));
|
|
1091
1298
|
writeFileAtomic(resolvedTargetConfigPath, content);
|
|
1092
1299
|
agentConfig.config_file = targetConfigPath;
|
|
1093
1300
|
if (resolvedExistingConfigFile &&
|