coding-tool-x 3.6.13 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +45 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
  4. package/dist/web/assets/Home-BwTaYu3q.js +1 -0
  5. package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
  6. package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
  7. package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
  8. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
  9. package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
  10. package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-Btfvh0GG.js +2 -0
  14. package/dist/web/assets/index-CLNa6DhH.css +1 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +3 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +46 -6
  22. package/src/commands/port-config.js +83 -75
  23. package/src/commands/proxy-control.js +59 -26
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/switch.js +5 -1
  26. package/src/commands/toggle-proxy.js +131 -53
  27. package/src/commands/ui.js +3 -4
  28. package/src/commands/workspace.js +3 -35
  29. package/src/config/default.js +19 -2
  30. package/src/config/loader.js +5 -76
  31. package/src/config/paths.js +96 -11
  32. package/src/index.js +10 -16
  33. package/src/server/api/commands.js +1 -1
  34. package/src/server/api/config-export.js +4 -2
  35. package/src/server/api/config-registry.js +40 -34
  36. package/src/server/api/config.js +5 -1
  37. package/src/server/api/dashboard.js +22 -6
  38. package/src/server/api/mcp.js +5 -3
  39. package/src/server/api/omp-channels.js +349 -0
  40. package/src/server/api/omp-config.js +164 -0
  41. package/src/server/api/omp-projects.js +76 -0
  42. package/src/server/api/omp-proxy.js +154 -0
  43. package/src/server/api/omp-sessions.js +334 -0
  44. package/src/server/api/omp-statistics.js +40 -0
  45. package/src/server/api/plugins.js +1 -1
  46. package/src/server/api/prompts.js +3 -2
  47. package/src/server/api/settings.js +2 -0
  48. package/src/server/api/skills.js +1 -1
  49. package/src/server/api/workspaces.js +3 -41
  50. package/src/server/index.js +41 -0
  51. package/src/server/omp-proxy-server.js +66 -0
  52. package/src/server/services/agents-service.js +2 -1
  53. package/src/server/services/base/proxy-utils.js +1 -0
  54. package/src/server/services/channel-balance.js +151 -1
  55. package/src/server/services/channel-scheduler.js +7 -1
  56. package/src/server/services/commands-service.js +11 -2
  57. package/src/server/services/config-export-service.js +296 -39
  58. package/src/server/services/config-registry-service.js +20 -18
  59. package/src/server/services/config-sync-manager.js +115 -6
  60. package/src/server/services/config-sync-service.js +4 -2
  61. package/src/server/services/config-templates-service.js +183 -45
  62. package/src/server/services/mcp-service.js +217 -33
  63. package/src/server/services/notification-hooks.js +138 -6
  64. package/src/server/services/omp-auth-providers.js +294 -0
  65. package/src/server/services/omp-channels.js +94 -0
  66. package/src/server/services/omp-config.js +269 -0
  67. package/src/server/services/omp-sessions.js +490 -0
  68. package/src/server/services/omp-settings-manager.js +975 -0
  69. package/src/server/services/omp-statistics-service.js +317 -0
  70. package/src/server/services/plugins-service.js +673 -11
  71. package/src/server/services/prompts-service.js +112 -7
  72. package/src/server/services/proxy-log-helper.js +1 -0
  73. package/src/server/services/proxy-runtime.js +5 -1
  74. package/src/server/services/sessions.js +19 -35
  75. package/src/server/services/skill-service.js +13 -3
  76. package/src/server/services/speed-test.js +34 -28
  77. package/src/server/services/ui-config.js +50 -12
  78. package/src/server/services/workspace-service.js +117 -225
  79. package/src/server/websocket-server.js +7 -1
  80. package/src/shared/platforms.js +201 -0
  81. package/src/utils/session.js +4 -10
  82. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  83. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  84. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  85. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  86. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  87. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  88. package/src/utils/cli-flags.js +0 -7
@@ -1,6 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { PATHS } = require('../../config/paths');
4
+ const {
5
+ DEFAULT_HOME_CLI_COLUMNS,
6
+ normalizeCustomCliPlatforms,
7
+ normalizeHomeCliColumns
8
+ } = require('../../shared/platforms');
4
9
 
5
10
  const UI_CONFIG_FILE = PATHS.uiConfig;
6
11
 
@@ -17,18 +22,27 @@ const DEFAULT_UI_CONFIG = {
17
22
  channelLocks: {
18
23
  claude: false,
19
24
  codex: false,
20
- gemini: false
25
+ gemini: false,
26
+ opencode: false,
27
+ omp: false
21
28
  },
22
29
  channelCollapse: {
23
30
  claude: [],
24
31
  codex: [],
25
- gemini: []
32
+ gemini: [],
33
+ opencode: [],
34
+ omp: []
26
35
  },
27
36
  channelOrder: {
28
37
  claude: [],
29
38
  codex: [],
30
- gemini: []
31
- }
39
+ gemini: [],
40
+ opencode: [],
41
+ omp: []
42
+ },
43
+ dashboardChannelOrder: DEFAULT_HOME_CLI_COLUMNS,
44
+ homeCliColumns: DEFAULT_HOME_CLI_COLUMNS,
45
+ customCliPlatforms: []
32
46
  };
33
47
 
34
48
  // 内存缓存
@@ -55,7 +69,7 @@ function readUIConfigFromFile() {
55
69
  const content = fs.readFileSync(UI_CONFIG_FILE, 'utf8');
56
70
  const data = JSON.parse(content);
57
71
  // Merge with defaults to ensure all keys exist
58
- return {
72
+ return normalizeUIConfig({
59
73
  ...DEFAULT_UI_CONFIG,
60
74
  ...data,
61
75
  theme: data.theme || DEFAULT_UI_CONFIG.theme,
@@ -64,13 +78,34 @@ function readUIConfigFromFile() {
64
78
  channelLocks: { ...DEFAULT_UI_CONFIG.channelLocks, ...data.channelLocks },
65
79
  channelCollapse: { ...DEFAULT_UI_CONFIG.channelCollapse, ...data.channelCollapse },
66
80
  channelOrder: { ...DEFAULT_UI_CONFIG.channelOrder, ...data.channelOrder }
67
- };
81
+ });
68
82
  } catch (error) {
69
83
  console.error('Error loading UI config:', error);
70
- return { ...DEFAULT_UI_CONFIG };
84
+ return normalizeUIConfig({ ...DEFAULT_UI_CONFIG });
71
85
  }
72
86
  }
73
87
 
88
+ function normalizeUIConfig(config = {}) {
89
+ const customCliPlatforms = normalizeCustomCliPlatforms(config.customCliPlatforms);
90
+ const legacyOrder = Array.isArray(config.homeCliColumns)
91
+ ? config.homeCliColumns
92
+ : config.dashboardChannelOrder;
93
+ const homeCliColumns = normalizeHomeCliColumns(legacyOrder, customCliPlatforms);
94
+
95
+ return {
96
+ ...DEFAULT_UI_CONFIG,
97
+ ...config,
98
+ panelVisibility: { ...DEFAULT_UI_CONFIG.panelVisibility, ...(config.panelVisibility || {}) },
99
+ channelBalance: { ...DEFAULT_UI_CONFIG.channelBalance, ...(config.channelBalance || {}) },
100
+ channelLocks: { ...DEFAULT_UI_CONFIG.channelLocks, ...(config.channelLocks || {}) },
101
+ channelCollapse: { ...DEFAULT_UI_CONFIG.channelCollapse, ...(config.channelCollapse || {}) },
102
+ channelOrder: { ...DEFAULT_UI_CONFIG.channelOrder, ...(config.channelOrder || {}) },
103
+ dashboardChannelOrder: homeCliColumns,
104
+ homeCliColumns,
105
+ customCliPlatforms
106
+ };
107
+ }
108
+
74
109
  // 初始化缓存(延迟初始化)
75
110
  function initializeCache() {
76
111
  if (cacheInitialized) return;
@@ -100,9 +135,10 @@ function saveUIConfig(config) {
100
135
  ensureConfigDir();
101
136
 
102
137
  try {
103
- fs.writeFileSync(UI_CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
138
+ const normalizedConfig = normalizeUIConfig(config);
139
+ fs.writeFileSync(UI_CONFIG_FILE, JSON.stringify(normalizedConfig, null, 2), 'utf8');
104
140
  // 同时更新缓存
105
- uiConfigCache = JSON.parse(JSON.stringify(config));
141
+ uiConfigCache = JSON.parse(JSON.stringify(normalizedConfig));
106
142
  } catch (error) {
107
143
  console.error('Error saving UI config:', error);
108
144
  throw error;
@@ -114,7 +150,7 @@ function updateUIConfig(key, value) {
114
150
  const config = loadUIConfig();
115
151
  config[key] = value;
116
152
  saveUIConfig(config);
117
- return config;
153
+ return loadUIConfig();
118
154
  }
119
155
 
120
156
  // Update nested config
@@ -125,12 +161,14 @@ function updateNestedUIConfig(parentKey, childKey, value) {
125
161
  }
126
162
  config[parentKey][childKey] = value;
127
163
  saveUIConfig(config);
128
- return config;
164
+ return loadUIConfig();
129
165
  }
130
166
 
131
167
  module.exports = {
132
168
  loadUIConfig,
133
169
  saveUIConfig,
134
170
  updateUIConfig,
135
- updateNestedUIConfig
171
+ updateNestedUIConfig,
172
+ normalizeUIConfig,
173
+ DEFAULT_UI_CONFIG
136
174
  };
@@ -1,6 +1,5 @@
1
1
  // 多项目工作区服务
2
2
  const fs = require('fs');
3
- const os = require('os');
4
3
  const path = require('path');
5
4
  const { execFileSync } = require('child_process');
6
5
  const { PATHS } = require('../../config/paths');
@@ -47,206 +46,6 @@ function resolveCurrentBranch(repoPath) {
47
46
  }
48
47
  }
49
48
 
50
- function getGitErrorMessage(error) {
51
- const stderr = error && error.stderr ? error.stderr.toString().trim() : '';
52
- return stderr || (error && error.message) || String(error);
53
- }
54
-
55
- function normalizeString(value) {
56
- return typeof value === 'string' ? value.trim() : '';
57
- }
58
-
59
- function normalizeBranchMode(projectConfig = {}) {
60
- const branchMode = normalizeString(projectConfig.branchMode);
61
- if (branchMode === 'new' || branchMode === 'existing') {
62
- return branchMode;
63
- }
64
- return normalizeString(projectConfig.baseBranch) ? 'new' : 'existing';
65
- }
66
-
67
- function normalizeGitBranchRef(branchName) {
68
- return normalizeString(branchName).replace(/^refs\/heads\//, '');
69
- }
70
-
71
- function parseGitWorktrees(output) {
72
- const worktrees = [];
73
- const lines = output.split('\n');
74
- let current = {};
75
-
76
- for (const line of lines) {
77
- if (line.startsWith('worktree ')) {
78
- if (current.path) {
79
- worktrees.push(current);
80
- }
81
- current = { path: line.substring(9) };
82
- } else if (line.startsWith('branch ')) {
83
- current.branch = line.substring(7);
84
- } else if (line.startsWith('HEAD ')) {
85
- current.head = line.substring(5);
86
- }
87
- }
88
-
89
- if (current.path) {
90
- worktrees.push(current);
91
- }
92
-
93
- return worktrees;
94
- }
95
-
96
- function listGitWorktrees(repoPath) {
97
- const output = runGitCommand(['worktree', 'list', '--porcelain'], {
98
- cwd: repoPath
99
- });
100
- return parseGitWorktrees(output);
101
- }
102
-
103
- function hasGitRef(repoPath, ref) {
104
- try {
105
- runGitCommand(['show-ref', '--verify', '--quiet', ref], {
106
- cwd: repoPath,
107
- stdio: 'ignore'
108
- });
109
- return true;
110
- } catch (_) {
111
- return false;
112
- }
113
- }
114
-
115
- function findWorktreeForBranch(repoPath, branchName) {
116
- const normalizedBranch = normalizeGitBranchRef(branchName);
117
- const refName = `refs/heads/${normalizedBranch}`;
118
- return listGitWorktrees(repoPath).find(wt => wt.branch === refName);
119
- }
120
-
121
- function pullBaseBranch(worktreePath, baseBranch) {
122
- try {
123
- runGitCommand(['pull', '--ff-only'], { cwd: worktreePath });
124
- } catch (error) {
125
- try {
126
- runGitCommand(['remote', 'get-url', 'origin'], { cwd: worktreePath });
127
- } catch (_) {
128
- return;
129
- }
130
- const normalizedBaseBranch = normalizeGitBranchRef(baseBranch);
131
- runGitCommand(['pull', '--ff-only', 'origin', normalizedBaseBranch], {
132
- cwd: worktreePath
133
- });
134
- }
135
- }
136
-
137
- function removeTemporaryWorktree(repoPath, tempWorktreePath) {
138
- try {
139
- runGitCommand(['worktree', 'remove', tempWorktreePath, '--force'], {
140
- cwd: repoPath
141
- });
142
- } catch (error) {
143
- console.warn(`移除临时 worktree 失败: ${tempWorktreePath}`, error.message);
144
- } finally {
145
- if (fs.existsSync(tempWorktreePath)) {
146
- fs.rmSync(tempWorktreePath, { recursive: true, force: true });
147
- }
148
- }
149
- }
150
-
151
- function updateBaseBranch(repoPath, baseBranch) {
152
- const normalizedBaseBranch = normalizeGitBranchRef(baseBranch);
153
- if (!normalizedBaseBranch) {
154
- return null;
155
- }
156
-
157
- runGitCommand(['fetch', '--all', '--prune'], { cwd: repoPath });
158
-
159
- const localRef = `refs/heads/${normalizedBaseBranch}`;
160
- const remoteRef = `refs/remotes/origin/${normalizedBaseBranch}`;
161
- const hasLocalBranch = hasGitRef(repoPath, localRef);
162
- const hasRemoteBranch = hasGitRef(repoPath, remoteRef);
163
- if (!hasLocalBranch && !hasRemoteBranch) {
164
- throw new Error(`基础分支不存在: ${normalizedBaseBranch}`);
165
- }
166
-
167
- const existingWorktree = hasLocalBranch ? findWorktreeForBranch(repoPath, normalizedBaseBranch) : null;
168
- if (existingWorktree) {
169
- pullBaseBranch(existingWorktree.path, normalizedBaseBranch);
170
- return normalizedBaseBranch;
171
- }
172
-
173
- const tempParent = fs.mkdtempSync(path.join(os.tmpdir(), 'ctx-base-'));
174
- const tempWorktreePath = path.join(tempParent, 'worktree');
175
- try {
176
- if (hasLocalBranch) {
177
- runGitCommand(['worktree', 'add', tempWorktreePath, normalizedBaseBranch], {
178
- cwd: repoPath
179
- });
180
- } else {
181
- runGitCommand(['worktree', 'add', tempWorktreePath, '-b', normalizedBaseBranch, `origin/${normalizedBaseBranch}`], {
182
- cwd: repoPath
183
- });
184
- }
185
- pullBaseBranch(tempWorktreePath, normalizedBaseBranch);
186
- return normalizedBaseBranch;
187
- } finally {
188
- removeTemporaryWorktree(repoPath, tempWorktreePath);
189
- if (fs.existsSync(tempParent)) {
190
- fs.rmSync(tempParent, { recursive: true, force: true });
191
- }
192
- }
193
- }
194
-
195
- function createGitWorktree({ sourcePath, worktreePath, branch, baseBranch, branchMode, actionLabel }) {
196
- let targetBranch = normalizeString(branch);
197
- if (!targetBranch) {
198
- try {
199
- targetBranch = resolveCurrentBranch(sourcePath);
200
- } catch (e) {
201
- targetBranch = 'main';
202
- }
203
- }
204
-
205
- const resolvedBranchMode = branchMode === 'new' ? 'new' : 'existing';
206
-
207
- if (resolvedBranchMode === 'new') {
208
- const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
209
- const updatedBaseBranch = updateBaseBranch(sourcePath, baseBranch);
210
- if (updatedBaseBranch) {
211
- worktreeArgs.push(updatedBaseBranch);
212
- }
213
-
214
- try {
215
- runGitCommand(worktreeArgs, { cwd: sourcePath });
216
- return { branch: targetBranch, path: worktreePath };
217
- } catch (error) {
218
- throw new Error(`创建 worktree 失败: ${getGitErrorMessage(error)}`);
219
- }
220
- }
221
-
222
- try {
223
- runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
224
- cwd: sourcePath
225
- });
226
- return { branch: targetBranch, path: worktreePath };
227
- } catch (error) {
228
- const message = getGitErrorMessage(error);
229
- if (/already (checked out|used by worktree)/i.test(message)) {
230
- try {
231
- runGitCommand(['worktree', 'add', '--force', worktreePath, targetBranch], {
232
- cwd: sourcePath
233
- });
234
- return { branch: targetBranch, path: worktreePath };
235
- } catch (forceError) {
236
- throw new Error(
237
- `无法${actionLabel}:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
238
- `仓库路径: ${sourcePath}\n` +
239
- `错误详情: ${getGitErrorMessage(forceError)}\n\n` +
240
- `解决方案:\n` +
241
- `1. 指定不同的分支名\n` +
242
- `2. 或者禁用 worktree 模式(设置 createWorktree: false)`
243
- );
244
- }
245
- }
246
- throw new Error(`创建 worktree 失败: ${message}`);
247
- }
248
- }
249
-
250
49
  /**
251
50
  * 生成唯一工作区 ID
252
51
  */
@@ -348,7 +147,31 @@ function getGitWorktrees(repoPath) {
348
147
  if (!isGitRepo(repoPath)) {
349
148
  return [];
350
149
  }
351
- const worktrees = listGitWorktrees(repoPath);
150
+ const output = runGitCommand(['worktree', 'list', '--porcelain'], {
151
+ cwd: repoPath
152
+ });
153
+
154
+ const worktrees = [];
155
+ const lines = output.split('\n');
156
+ let current = {};
157
+
158
+ for (const line of lines) {
159
+ if (line.startsWith('worktree ')) {
160
+ if (current.path) {
161
+ worktrees.push(current);
162
+ }
163
+ current = { path: line.substring(9) };
164
+ } else if (line.startsWith('branch ')) {
165
+ current.branch = line.substring(7);
166
+ } else if (line.startsWith('HEAD ')) {
167
+ current.head = line.substring(5);
168
+ }
169
+ }
170
+
171
+ if (current.path) {
172
+ worktrees.push(current);
173
+ }
174
+
352
175
  return worktrees.filter(wt => wt.path !== repoPath); // 排除主仓库
353
176
  } catch (error) {
354
177
  console.error('获取 worktree 列表失败:', error.message);
@@ -485,19 +308,48 @@ function createWorkspace(options) {
485
308
 
486
309
  // Git 仓库且需要创建 worktree:直接在工作区目录内创建,无需额外 symlink
487
310
  if (isGit && useWorktree) {
311
+ // 获取当前分支作为默认分支
312
+ let targetBranch = branch;
313
+ if (!targetBranch) {
314
+ try {
315
+ targetBranch = resolveCurrentBranch(sourcePath);
316
+ } catch (e) {
317
+ targetBranch = 'main';
318
+ }
319
+ }
320
+
488
321
  // worktree 直接创建到工作区目录内的项目路径
489
322
  const worktreePath = symlinkPath;
490
- const createdWorktree = createGitWorktree({
491
- sourcePath,
492
- worktreePath,
493
- branch,
494
- baseBranch,
495
- branchMode: normalizeBranchMode(proj),
496
- actionLabel: '创建 worktree'
497
- });
323
+
324
+ try {
325
+ // 尝试检出已有分支
326
+ runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
327
+ cwd: sourcePath
328
+ });
329
+ } catch (error) {
330
+ // 如果分支不存在,尝试创建新分支
331
+ try {
332
+ const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
333
+ if (baseBranch && baseBranch.trim()) {
334
+ worktreeArgs.push(baseBranch.trim());
335
+ }
336
+ runGitCommand(worktreeArgs, {
337
+ cwd: sourcePath
338
+ });
339
+ } catch (err) {
340
+ if (err.message.includes('already checked out')) {
341
+ throw new Error(
342
+ `无法创建 worktree:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
343
+ `错误详情: ${err.message}\n\n` +
344
+ `提示:请为此项目指定不同的分支名,或禁用 worktree 模式。`
345
+ );
346
+ }
347
+ throw new Error(`创建 worktree 失败: ${err.message}`);
348
+ }
349
+ }
498
350
 
499
351
  targetPath = worktreePath;
500
- worktrees.push(createdWorktree);
352
+ worktrees.push({ branch: targetBranch, path: worktreePath });
501
353
  } else if (isGit) {
502
354
  // Git 仓库但不创建 worktree:symlink 指向源路径,记录已有 worktrees
503
355
  worktrees = getGitWorktrees(sourcePath);
@@ -657,18 +509,44 @@ function addProjectToWorkspace(workspaceId, projectConfig) {
657
509
 
658
510
  // Git 仓库且需要创建 worktree:直接在工作区目录内创建,无需额外 symlink
659
511
  if (isGit && useWorktree) {
512
+ let targetBranch = branch;
513
+ if (!targetBranch) {
514
+ try {
515
+ targetBranch = resolveCurrentBranch(sourcePath);
516
+ } catch (e) {
517
+ targetBranch = 'main';
518
+ }
519
+ }
520
+
660
521
  const worktreePath = symlinkPath;
661
- const createdWorktree = createGitWorktree({
662
- sourcePath,
663
- worktreePath,
664
- branch,
665
- baseBranch,
666
- branchMode: normalizeBranchMode(projectConfig),
667
- actionLabel: '添加项目'
668
- });
522
+
523
+ try {
524
+ runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
525
+ cwd: sourcePath
526
+ });
527
+ } catch (error) {
528
+ try {
529
+ const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
530
+ if (baseBranch && baseBranch.trim()) {
531
+ worktreeArgs.push(baseBranch.trim());
532
+ }
533
+ runGitCommand(worktreeArgs, { cwd: sourcePath });
534
+ } catch (err) {
535
+ if (err.message && err.message.includes('already checked out')) {
536
+ throw new Error(
537
+ `无法添加项目:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
538
+ `仓库路径: ${sourcePath}\n\n` +
539
+ `解决方案:\n` +
540
+ `1. 指定不同的分支名\n` +
541
+ `2. 或者禁用 worktree 模式(设置 createWorktree: false)`
542
+ );
543
+ }
544
+ throw new Error(`创建 worktree 失败: ${err.message}`);
545
+ }
546
+ }
669
547
 
670
548
  targetPath = worktreePath;
671
- worktrees.push(createdWorktree);
549
+ worktrees.push({ branch: targetBranch, path: worktreePath });
672
550
  } else if (isGit) {
673
551
  // Git 仓库但不创建 worktree:symlink 指向源路径
674
552
  worktrees = getGitWorktrees(sourcePath);
@@ -743,7 +621,7 @@ function removeProjectFromWorkspace(workspaceId, projectName) {
743
621
 
744
622
 
745
623
  /**
746
- * 获取所有渠道(Claude/Codex/Gemini)的项目并集
624
+ * 获取所有渠道(Claude/Codex/Gemini/OpenCode/OMP)的项目并集
747
625
  * @returns {Promise<Array>} 去重后的项目列表
748
626
  */
749
627
  async function getAllAvailableProjects() {
@@ -752,6 +630,7 @@ async function getAllAvailableProjects() {
752
630
  const codexSessionsService = require('./codex-sessions');
753
631
  const geminiSessionsService = require('./gemini-sessions');
754
632
  const opencodeSessionsService = require('./opencode-sessions');
633
+ const ompSessionsService = require('./omp-sessions');
755
634
  const { isCodexInstalled } = require('./codex-config');
756
635
  const { isGeminiInstalled } = require('./gemini-config');
757
636
  const { isOpenCodeInstalled } = require('./opencode-sessions');
@@ -769,7 +648,7 @@ async function getAllAvailableProjects() {
769
648
  seenKeys.add(projectKey);
770
649
 
771
650
  const displayName = project.displayName || (projectPath ? path.basename(projectPath) : project.name);
772
- const lastUsedValue = project.lastUsed || project.lastUpdated || null;
651
+ const lastUsedValue = project.lastUsed || project.lastUpdated || project.mtimeMs || null;
773
652
  const lastUsed = typeof lastUsedValue === 'string'
774
653
  ? new Date(lastUsedValue).getTime()
775
654
  : (lastUsedValue || 0);
@@ -824,6 +703,16 @@ async function getAllAvailableProjects() {
824
703
  console.error('获取 opencode 项目失败:', error.message);
825
704
  }
826
705
 
706
+ try {
707
+ const ompProjects = ompSessionsService.getProjects();
708
+ const list = Array.isArray(ompProjects) ? ompProjects : [];
709
+ if (list.length > 0 || ompSessionsService.isOmpInstalled()) {
710
+ list.forEach(project => addProject('omp', project));
711
+ }
712
+ } catch (error) {
713
+ console.error('获取 OMP 项目失败:', error.message);
714
+ }
715
+
827
716
  // 按最后使用时间排序
828
717
  allProjects.sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0));
829
718
 
@@ -833,7 +722,7 @@ async function getAllAvailableProjects() {
833
722
  /**
834
723
  * 在工作区中启动 CLI 工具
835
724
  * @param {string} workspaceId 工作区 ID
836
- * @param {string} tool 工具名称 (claude/codex/gemini/opencode)
725
+ * @param {string} tool 工具名称 (claude/codex/gemini/opencode/omp/omp)
837
726
  * @param {string} projectName 可选,工作区内的项目名
838
727
  * @returns {object} 启动信息
839
728
  */
@@ -860,15 +749,18 @@ function getLaunchCommand(workspaceId, tool, projectName = null) {
860
749
  }
861
750
  }
862
751
 
752
+ const normalizedTool = tool === 'omp' ? 'omp' : tool;
753
+
863
754
  // 根据工具类型生成启动命令
864
755
  const commands = {
865
756
  claude: 'claude',
866
757
  codex: 'codex',
867
758
  gemini: 'gemini',
868
- opencode: 'opencode'
759
+ opencode: 'opencode',
760
+ omp: 'omp'
869
761
  };
870
762
 
871
- const cmd = commands[tool];
763
+ const cmd = commands[normalizedTool];
872
764
  if (!cmd) {
873
765
  throw new Error(`不支持的工具: ${tool}`);
874
766
  }
@@ -182,18 +182,24 @@ function inferSource(log) {
182
182
  if (log.toolType) {
183
183
  if (log.toolType.includes('codex')) return 'codex';
184
184
  if (log.toolType.includes('gemini')) return 'gemini';
185
+ if (log.toolType.includes('opencode')) return 'opencode';
186
+ if (log.toolType.includes('omp')) return 'omp';
185
187
  }
186
188
  if (typeof log.model === 'string') {
187
189
  const model = log.model.toLowerCase();
188
190
  if (model.includes('gemini')) return 'gemini';
191
+ if (model.includes('opencode')) return 'opencode';
192
+ if (model.includes('omp')) return 'omp';
189
193
  if (model.includes('gpt') || model.includes('o1') || model.includes('o3')) return 'codex';
190
194
  if (model.includes('claude')) return 'claude';
191
195
  }
192
196
  if (typeof log.action === 'string') {
193
197
  if (log.action.includes('codex')) return 'codex';
194
198
  if (log.action.includes('gemini')) return 'gemini';
199
+ if (log.action.includes('opencode')) return 'opencode';
200
+ if (log.action.includes('omp')) return 'omp';
195
201
  }
196
- if (log.channelType === 'codex' || log.channelType === 'gemini') {
202
+ if (log.channelType === 'codex' || log.channelType === 'gemini' || log.channelType === 'opencode' || log.channelType === 'omp') {
197
203
  return log.channelType;
198
204
  }
199
205
  return 'claude';