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.
Files changed (91) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -25
  3. package/bin/ctx.js +1 -6
  4. package/dist/web/assets/{Analytics-BzoNzfbi.js → Analytics-DPb1zErn.js} +2 -2
  5. package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
  6. package/dist/web/assets/{ConfigTemplates-O4ikBt1o.js → ConfigTemplates-Bki2k4Lo.js} +1 -1
  7. package/dist/web/assets/{Home-BQjsnblU.js → Home-BKYHKSax.js} +1 -1
  8. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  9. package/dist/web/assets/{PluginManager-DS_DJnVc.js → PluginManager-jgeQOE24.js} +1 -1
  10. package/dist/web/assets/ProjectList-CF67RzP_.js +1 -0
  11. package/dist/web/assets/ProjectList-DL4JK6ci.css +1 -0
  12. package/dist/web/assets/SessionList-DhHsY5q9.js +1 -0
  13. package/dist/web/assets/SessionList-lz23U6ZJ.css +1 -0
  14. package/dist/web/assets/{SkillManager-DpNE02r0.js → SkillManager-CtEz5gGp.js} +1 -1
  15. package/dist/web/assets/{WorkspaceManager-DMY7_SHh.js → WorkspaceManager-D3B0Y5XE.js} +1 -1
  16. package/dist/web/assets/icons-lKCTySl7.js +1 -0
  17. package/dist/web/assets/index-BS7aplYp.css +1 -0
  18. package/dist/web/assets/index-BnyNm74G.js +2 -0
  19. package/dist/web/assets/{markdown-DyTJGI4N.js → markdown-BNJfEgCA.js} +1 -1
  20. package/dist/web/assets/{naive-ui-Cg4_ZeoT.js → naive-ui-DM897mIP.js} +1 -1
  21. package/dist/web/assets/vendors-DQA4Hw9v.js +7 -0
  22. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +45 -0
  23. package/dist/web/index.html +8 -8
  24. package/package.json +1 -2
  25. package/src/commands/daemon.js +5 -33
  26. package/src/commands/export-config.js +6 -6
  27. package/src/commands/stats.js +4 -41
  28. package/src/commands/ui.js +3 -12
  29. package/src/config/channel-preset-websites.js +114 -0
  30. package/src/config/default.js +9 -5
  31. package/src/config/loader.js +2 -2
  32. package/src/config/model-metadata.json +16 -20
  33. package/src/config/paths.js +33 -166
  34. package/src/index.js +34 -125
  35. package/src/server/api/agents.js +2 -52
  36. package/src/server/api/codex-proxy.js +4 -2
  37. package/src/server/api/codex-sessions.js +88 -9
  38. package/src/server/api/commands.js +2 -38
  39. package/src/server/api/dashboard.js +1 -25
  40. package/src/server/api/gemini-proxy.js +4 -2
  41. package/src/server/api/gemini-sessions.js +86 -9
  42. package/src/server/api/hooks.js +1 -17
  43. package/src/server/api/opencode-proxy.js +7 -2
  44. package/src/server/api/opencode-sessions.js +82 -7
  45. package/src/server/api/plugins.js +34 -128
  46. package/src/server/api/proxy.js +5 -3
  47. package/src/server/api/sessions.js +180 -24
  48. package/src/server/api/workspaces.js +0 -14
  49. package/src/server/index.js +9 -31
  50. package/src/server/services/agents-service.js +62 -269
  51. package/src/server/services/base/base-channel-service.js +3 -1
  52. package/src/server/services/channels.js +5 -0
  53. package/src/server/services/codex-channels.js +2 -0
  54. package/src/server/services/codex-sessions.js +78 -108
  55. package/src/server/services/commands-service.js +81 -281
  56. package/src/server/services/config-export-service.js +7 -7
  57. package/src/server/services/config-registry-service.js +9 -8
  58. package/src/server/services/config-sync-manager.js +41 -61
  59. package/src/server/services/config-sync-service.js +3 -3
  60. package/src/server/services/gemini-channels.js +17 -6
  61. package/src/server/services/gemini-config.js +4 -3
  62. package/src/server/services/gemini-sessions.js +51 -26
  63. package/src/server/services/gemini-settings-manager.js +3 -2
  64. package/src/server/services/mcp-service.js +14 -9
  65. package/src/server/services/native-oauth-adapters.js +3 -3
  66. package/src/server/services/network-access.js +2 -61
  67. package/src/server/services/notification-hooks.js +19 -184
  68. package/src/server/services/opencode-channels.js +9 -0
  69. package/src/server/services/opencode-sessions.js +4 -4
  70. package/src/server/services/opencode-settings-manager.js +3 -3
  71. package/src/server/services/plugins-service.js +62 -996
  72. package/src/server/services/prompts-service.js +9 -5
  73. package/src/server/services/sessions.js +128 -99
  74. package/src/server/services/skill-service.js +18 -155
  75. package/src/server/websocket-server.js +4 -35
  76. package/dist/web/assets/Analytics-vQS5IWvs.css +0 -1
  77. package/dist/web/assets/Home-qzk118Of.css +0 -1
  78. package/dist/web/assets/ProjectList-CqYDtsHx.js +0 -1
  79. package/dist/web/assets/ProjectList-GCC2QOmq.css +0 -1
  80. package/dist/web/assets/SessionList-CfPtcq6Y.css +0 -1
  81. package/dist/web/assets/SessionList-DMlLtMCz.js +0 -1
  82. package/dist/web/assets/icons-CEq2hYB-.js +0 -1
  83. package/dist/web/assets/index-Clf0l3wc.js +0 -2
  84. package/dist/web/assets/index-Dih_bOsv.css +0 -1
  85. package/dist/web/assets/vendors-Bsp-dq2d.js +0 -7
  86. package/dist/web/assets/vue-vendor-BxIT0uQq.js +0 -45
  87. package/docs/Caddyfile.example +0 -19
  88. package/docs/reverse-proxy-https.md +0 -57
  89. package/src/server/services/https-cert.js +0 -171
  90. package/src/server/services/session-launch-command.js +0 -81
  91. 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
- NATIVE_PATHS,
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 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;
20
+ const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
30
21
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
31
- const CODEX_AGENTS_DIR = NATIVE_PATHS.codex.agents;
32
- const CLAUDE_AGENTS_DIR = NATIVE_PATHS.claude.agents;
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: OPENCODE_AGENTS_DIR,
44
- storageDir: PATHS.localAgents.opencode,
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(getNativePathDir(CODEX_CONFIG_PATH));
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 joinNativeBasePath(CODEX_AGENTS_DIR, `${fileName}.toml`);
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 (isNativeAbsolutePath(normalized)) {
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 joinNativeBasePath(HOME_DIR, normalized.slice(2));
212
+ return path.join(HOME_DIR, normalized.slice(2));
224
213
  }
225
214
 
226
- if (isNativeAbsolutePath(normalized)) {
215
+ if (path.isAbsolute(normalized)) {
227
216
  return normalized;
228
217
  }
229
218
 
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}/`);
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 = resolveNativeBasePath(CODEX_AGENTS_DIR);
251
- return isPathInsideNativeDir(resolved, managedRoot);
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, options = {}) {
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
- const agent = {
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, options = {}) {
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
- this.mergeInstalledUserAgents(agents, { syncManagedLocalAgents });
706
- this.mergeLocalUserAgents(agents);
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 (!activePath) {
656
+ if (!fs.existsSync(filePath)) {
810
657
  return null;
811
658
  }
812
659
 
813
- const content = fs.readFileSync(activePath, 'utf-8');
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: activePath,
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
- installed,
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) || (managedPath && fs.existsSync(managedPath))) {
707
+ if (fs.existsSync(filePath)) {
867
708
  throw new Error(`代理 "${fileName}" 已存在`);
868
709
  }
869
710
 
870
- const content = this.buildAgentContent({
871
- fileName,
872
- name,
873
- description,
874
- tools,
875
- model,
876
- permissionMode,
877
- skills,
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) && !hasManagedCopy) {
741
+ if (!fs.existsSync(filePath)) {
909
742
  throw new Error(`代理 "${fileName}" 不存在`);
910
743
  }
911
744
 
912
- const content = this.buildAgentContent({
913
- fileName,
914
- name,
915
- description,
916
- tools,
917
- model,
918
- permissionMode,
919
- skills,
920
- systemPrompt
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
- 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
- }
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
- 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' };
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(getNativePathDir(resolvedPath));
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(getNativePathDir(resolvedConfigFilePath));
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(getNativePathDir(resolvedConfigFilePath));
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(getNativePathDir(resolvedTargetConfigPath));
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,