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.
- package/README.md +45 -33
- package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
- package/dist/web/assets/Home-BwTaYu3q.js +1 -0
- package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
- package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
- package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
- package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
- package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-Btfvh0GG.js +2 -0
- package/dist/web/assets/index-CLNa6DhH.css +1 -0
- package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +3 -1
- package/src/commands/channels.js +85 -24
- package/src/commands/daemon.js +7 -4
- package/src/commands/doctor.js +17 -8
- package/src/commands/logs.js +46 -6
- package/src/commands/port-config.js +83 -75
- package/src/commands/proxy-control.js +59 -26
- package/src/commands/stats.js +37 -19
- package/src/commands/switch.js +5 -1
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/commands/workspace.js +3 -35
- package/src/config/default.js +19 -2
- package/src/config/loader.js +5 -76
- package/src/config/paths.js +96 -11
- package/src/index.js +10 -16
- package/src/server/api/commands.js +1 -1
- package/src/server/api/config-export.js +4 -2
- package/src/server/api/config-registry.js +40 -34
- package/src/server/api/config.js +5 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/omp-channels.js +349 -0
- package/src/server/api/omp-config.js +164 -0
- package/src/server/api/omp-projects.js +76 -0
- package/src/server/api/omp-proxy.js +154 -0
- package/src/server/api/omp-sessions.js +334 -0
- package/src/server/api/omp-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/settings.js +2 -0
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +3 -41
- package/src/server/index.js +41 -0
- package/src/server/omp-proxy-server.js +66 -0
- package/src/server/services/agents-service.js +2 -1
- package/src/server/services/base/proxy-utils.js +1 -0
- package/src/server/services/channel-balance.js +151 -1
- package/src/server/services/channel-scheduler.js +7 -1
- package/src/server/services/commands-service.js +11 -2
- package/src/server/services/config-export-service.js +296 -39
- package/src/server/services/config-registry-service.js +20 -18
- package/src/server/services/config-sync-manager.js +115 -6
- package/src/server/services/config-sync-service.js +4 -2
- package/src/server/services/config-templates-service.js +183 -45
- package/src/server/services/mcp-service.js +217 -33
- package/src/server/services/notification-hooks.js +138 -6
- package/src/server/services/omp-auth-providers.js +294 -0
- package/src/server/services/omp-channels.js +94 -0
- package/src/server/services/omp-config.js +269 -0
- package/src/server/services/omp-sessions.js +490 -0
- package/src/server/services/omp-settings-manager.js +975 -0
- package/src/server/services/omp-statistics-service.js +317 -0
- package/src/server/services/plugins-service.js +673 -11
- package/src/server/services/prompts-service.js +112 -7
- package/src/server/services/proxy-log-helper.js +1 -0
- package/src/server/services/proxy-runtime.js +5 -1
- package/src/server/services/sessions.js +19 -35
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +34 -28
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +117 -225
- package/src/server/websocket-server.js +7 -1
- package/src/shared/platforms.js +201 -0
- package/src/utils/session.js +4 -10
- package/dist/web/assets/Home-BiaKZMah.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
- package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
- package/dist/web/assets/index-BpBFF3b2.js +0 -2
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- 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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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(
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
worktreePath,
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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(
|
|
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[
|
|
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';
|