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.
Files changed (60) hide show
  1. package/dist/web/assets/{Analytics-C6DEmD3D.js → Analytics-C5W3axXs.js} +2 -2
  2. package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
  3. package/dist/web/assets/{ConfigTemplates-Cf_iTpC4.js → ConfigTemplates-DzyVFDx9.js} +1 -1
  4. package/dist/web/assets/{Home-BtBmYLJ1.js → Home-C9TQNB6f.js} +1 -1
  5. package/dist/web/assets/Home-qzk118Of.css +1 -0
  6. package/dist/web/assets/{PluginManager-DEk8vSw5.js → PluginManager-9B_brLWT.js} +1 -1
  7. package/dist/web/assets/ProjectList-Bjt6mrsV.js +1 -0
  8. package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
  9. package/dist/web/assets/SessionList-BsHPgmUR.css +1 -0
  10. package/dist/web/assets/SessionList-DcBH13uA.js +1 -0
  11. package/dist/web/assets/{SkillManager-DcZOiiSf.js → SkillManager-vST8DRRg.js} +1 -1
  12. package/dist/web/assets/{WorkspaceManager-BHqI8aGV.js → WorkspaceManager-ov1KgRXR.js} +1 -1
  13. package/dist/web/assets/icons-CEq2hYB-.js +1 -0
  14. package/dist/web/assets/index-Dih_bOsv.css +1 -0
  15. package/dist/web/assets/index-Duc7QP4e.js +2 -0
  16. package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
  17. package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
  18. package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
  19. package/dist/web/index.html +7 -7
  20. package/package.json +1 -1
  21. package/src/commands/export-config.js +6 -6
  22. package/src/config/default.js +2 -6
  23. package/src/config/loader.js +2 -2
  24. package/src/config/paths.js +160 -33
  25. package/src/server/api/agents.js +52 -2
  26. package/src/server/api/codex-sessions.js +4 -2
  27. package/src/server/api/commands.js +38 -2
  28. package/src/server/api/opencode-sessions.js +4 -2
  29. package/src/server/api/plugins.js +104 -1
  30. package/src/server/api/sessions.js +9 -7
  31. package/src/server/services/agents-service.js +269 -62
  32. package/src/server/services/commands-service.js +281 -81
  33. package/src/server/services/config-export-service.js +7 -7
  34. package/src/server/services/config-registry-service.js +4 -5
  35. package/src/server/services/config-sync-manager.js +61 -41
  36. package/src/server/services/config-sync-service.js +3 -3
  37. package/src/server/services/gemini-channels.js +5 -5
  38. package/src/server/services/gemini-config.js +3 -4
  39. package/src/server/services/gemini-sessions.js +23 -20
  40. package/src/server/services/gemini-settings-manager.js +2 -3
  41. package/src/server/services/mcp-service.js +9 -14
  42. package/src/server/services/native-oauth-adapters.js +3 -3
  43. package/src/server/services/notification-hooks.js +3 -3
  44. package/src/server/services/opencode-sessions.js +16 -6
  45. package/src/server/services/opencode-settings-manager.js +3 -3
  46. package/src/server/services/plugins-service.js +499 -23
  47. package/src/server/services/prompts-service.js +5 -9
  48. package/src/server/services/session-launch-command.js +1 -24
  49. package/src/server/services/sessions.js +91 -40
  50. package/src/server/services/skill-service.js +155 -18
  51. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  52. package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
  53. package/dist/web/assets/ProjectList-BMVhA_Kh.js +0 -1
  54. package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
  55. package/dist/web/assets/SessionList-B5ioAXxg.js +0 -1
  56. package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
  57. package/dist/web/assets/icons-CQuif85v.js +0 -1
  58. package/dist/web/assets/index-CtByKdkA.js +0 -2
  59. package/dist/web/assets/index-VGAxnLqi.css +0 -1
  60. 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 { NATIVE_PATHS } = require('../../config/paths');
15
- const { resolvePreferredHomeDir } = require('../../utils/home-dir');
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 OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
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 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');
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: path.join(OPENCODE_CONFIG_DIR, 'agents'),
35
- legacyUserAgentsDir: path.join(OPENCODE_CONFIG_DIR, 'agent'),
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(path.dirname(CODEX_CONFIG_PATH));
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 path.join(CODEX_AGENTS_DIR, `${fileName}.toml`);
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 (path.isAbsolute(normalized)) {
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 path.join(HOME_DIR, normalized.slice(2));
223
+ return joinNativeBasePath(HOME_DIR, normalized.slice(2));
213
224
  }
214
225
 
215
- if (path.isAbsolute(normalized)) {
226
+ if (isNativeAbsolutePath(normalized)) {
216
227
  return normalized;
217
228
  }
218
229
 
219
- return path.resolve(path.dirname(CODEX_CONFIG_PATH), normalized);
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 = path.resolve(CODEX_AGENTS_DIR) + path.sep;
226
- return resolved.startsWith(managedRoot) || resolved === path.resolve(CODEX_AGENTS_DIR);
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
- agents.push({
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
- const userAgents = scanAgentsDir(this.userAgentsDir, this.userAgentsDir, 'user');
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: userAgents.length,
583
- projectCount: agents.length - userAgents.length
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 (!fs.existsSync(filePath)) {
809
+ if (!activePath) {
657
810
  return null;
658
811
  }
659
812
 
660
- const content = fs.readFileSync(filePath, 'utf-8');
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: filePath,
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
- updatedAt: fs.statSync(filePath).mtime.getTime()
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
- 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 || '');
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
- 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;
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.writeFileSync(filePath, content, 'utf-8');
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 (!fs.existsSync(filePath)) {
779
- return { success: false, message: '代理不存在' };
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
- fs.unlinkSync(filePath);
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
- return this.repoScanner.uninstall(`${fileName}.md`);
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(path.dirname(resolvedPath));
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(path.dirname(resolvedConfigFilePath));
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(path.dirname(resolvedConfigFilePath));
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(path.dirname(resolvedTargetConfigPath));
1297
+ ensureDir(getNativePathDir(resolvedTargetConfigPath));
1091
1298
  writeFileAtomic(resolvedTargetConfigPath, content);
1092
1299
  agentConfig.config_file = targetConfigPath;
1093
1300
  if (resolvedExistingConfigFile &&