coding-tool-x 3.5.1 → 3.5.2
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/package.json
CHANGED
package/src/commands/daemon.js
CHANGED
|
@@ -3,7 +3,13 @@ const path = require('path');
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { loadConfig } = require('../config/loader');
|
|
5
5
|
const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
findProcessByPort,
|
|
8
|
+
killProcessByPort,
|
|
9
|
+
waitForPortRelease,
|
|
10
|
+
getPortToolIssue,
|
|
11
|
+
formatPortToolIssue
|
|
12
|
+
} = require('../utils/port-helper');
|
|
7
13
|
|
|
8
14
|
const PM2_APP_NAME = 'cc-tool';
|
|
9
15
|
|
|
@@ -83,6 +89,50 @@ function shouldTreatPortOwnershipAsReady(ownsPort) {
|
|
|
83
89
|
return ownsPort === true || ownsPort === null;
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
function getManagedPorts(config = loadConfig()) {
|
|
93
|
+
return [
|
|
94
|
+
config.ports?.webUI || 19999,
|
|
95
|
+
config.ports?.proxy || 20088,
|
|
96
|
+
config.ports?.codexProxy || 20089,
|
|
97
|
+
config.ports?.geminiProxy || 20090,
|
|
98
|
+
config.ports?.opencodeProxy || 20091
|
|
99
|
+
].filter((port, index, list) => Number.isInteger(port) && port > 0 && list.indexOf(port) === index);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function cleanupManagedPorts(config = loadConfig(), options = {}) {
|
|
103
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 3000;
|
|
104
|
+
const ports = getManagedPorts(config);
|
|
105
|
+
const released = [];
|
|
106
|
+
const forced = [];
|
|
107
|
+
const stillInUse = [];
|
|
108
|
+
|
|
109
|
+
for (const port of ports) {
|
|
110
|
+
if (await waitForPortRelease(port, timeoutMs)) {
|
|
111
|
+
released.push(port);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const killed = killProcessByPort(port);
|
|
116
|
+
if (killed) {
|
|
117
|
+
forced.push(port);
|
|
118
|
+
if (await waitForPortRelease(port, timeoutMs)) {
|
|
119
|
+
released.push(port);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
stillInUse.push(port);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
ports,
|
|
129
|
+
released,
|
|
130
|
+
forced,
|
|
131
|
+
stillInUse,
|
|
132
|
+
toolIssue: getPortToolIssue()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
86
136
|
async function waitForServiceReady(port, timeoutMs = 15000, intervalMs = 500) {
|
|
87
137
|
const startAt = Date.now();
|
|
88
138
|
let lastProcess = null;
|
|
@@ -232,10 +282,21 @@ async function handleStart() {
|
|
|
232
282
|
async function handleStop() {
|
|
233
283
|
try {
|
|
234
284
|
await connectPM2();
|
|
285
|
+
const config = loadConfig();
|
|
235
286
|
|
|
236
287
|
const existing = await getCCToolProcess();
|
|
237
288
|
if (!existing) {
|
|
289
|
+
const cleanup = await cleanupManagedPorts(config, { timeoutMs: 3000 });
|
|
238
290
|
console.log(chalk.yellow('\n[WARN] 服务未在运行\n'));
|
|
291
|
+
|
|
292
|
+
if (cleanup.forced.length > 0) {
|
|
293
|
+
console.log(chalk.yellow(`[WARN] 已额外清理残留端口: ${cleanup.forced.join(', ')}`));
|
|
294
|
+
}
|
|
295
|
+
if (cleanup.stillInUse.length > 0) {
|
|
296
|
+
console.log(chalk.red(`[ERROR] 以下端口仍被占用: ${cleanup.stillInUse.join(', ')}`));
|
|
297
|
+
printPortToolIssue(cleanup.toolIssue);
|
|
298
|
+
console.log(chalk.yellow('[TIP] 请检查是否有外部进程仍占用这些端口\n'));
|
|
299
|
+
}
|
|
239
300
|
disconnectPM2();
|
|
240
301
|
return;
|
|
241
302
|
}
|
|
@@ -248,11 +309,21 @@ async function handleStop() {
|
|
|
248
309
|
}
|
|
249
310
|
|
|
250
311
|
// 删除进程
|
|
251
|
-
pm2.delete(PM2_APP_NAME, (err) => {
|
|
312
|
+
pm2.delete(PM2_APP_NAME, async (err) => {
|
|
252
313
|
if (err) {
|
|
253
314
|
console.error(chalk.red('删除进程失败:'), err.message);
|
|
254
315
|
} else {
|
|
316
|
+
const cleanup = await cleanupManagedPorts(config, { timeoutMs: 3000 });
|
|
255
317
|
console.log(chalk.green('\n[OK] Coding-Tool 服务已停止\n'));
|
|
318
|
+
|
|
319
|
+
if (cleanup.forced.length > 0) {
|
|
320
|
+
console.log(chalk.yellow(`[WARN] 已额外清理残留端口: ${cleanup.forced.join(', ')}`));
|
|
321
|
+
}
|
|
322
|
+
if (cleanup.stillInUse.length > 0) {
|
|
323
|
+
console.log(chalk.red(`[ERROR] 以下端口仍被占用: ${cleanup.stillInUse.join(', ')}`));
|
|
324
|
+
printPortToolIssue(cleanup.toolIssue);
|
|
325
|
+
console.log(chalk.yellow('[TIP] 请检查是否有外部进程仍占用这些端口\n'));
|
|
326
|
+
}
|
|
256
327
|
}
|
|
257
328
|
|
|
258
329
|
pm2.dump((err) => {
|
|
@@ -399,6 +470,7 @@ module.exports = {
|
|
|
399
470
|
handleRestart,
|
|
400
471
|
handleStatus,
|
|
401
472
|
_test: {
|
|
402
|
-
shouldTreatPortOwnershipAsReady
|
|
473
|
+
shouldTreatPortOwnershipAsReady,
|
|
474
|
+
getManagedPorts
|
|
403
475
|
}
|
|
404
476
|
};
|
|
@@ -37,6 +37,33 @@ function hasBackup() {
|
|
|
37
37
|
return fs.existsSync(getConfigBackupPath()) || fs.existsSync(getAuthBackupPath());
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function isRecoverableEnvSyncError(err) {
|
|
41
|
+
const message = String(err?.message || '');
|
|
42
|
+
return err?.code === 'ETIMEDOUT' ||
|
|
43
|
+
/timed out|spawnSync (?:powershell|pwsh)|No PowerShell executable available/i.test(message);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function trySyncCodexUserEnvironment(envMap, options) {
|
|
47
|
+
try {
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
result: syncCodexUserEnvironment(envMap, options),
|
|
51
|
+
warning: null
|
|
52
|
+
};
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (!isRecoverableEnvSyncError(err)) {
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.warn(`[Codex Settings] 跳过持久化环境变量同步: ${err.message}`);
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
result: null,
|
|
62
|
+
warning: err.message
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
40
67
|
|
|
41
68
|
// 读取 config.toml
|
|
42
69
|
function readConfig() {
|
|
@@ -191,7 +218,7 @@ function restoreSettings() {
|
|
|
191
218
|
fs.unlinkSync(getAuthBackupPath());
|
|
192
219
|
}
|
|
193
220
|
|
|
194
|
-
|
|
221
|
+
const envSync = trySyncCodexUserEnvironment({}, {
|
|
195
222
|
replace: false,
|
|
196
223
|
removeKeys: ['CC_PROXY_KEY']
|
|
197
224
|
});
|
|
@@ -200,7 +227,11 @@ function restoreSettings() {
|
|
|
200
227
|
delete process.env.CC_PROXY_KEY;
|
|
201
228
|
|
|
202
229
|
console.log('Codex settings restored from backup');
|
|
203
|
-
|
|
230
|
+
const result = { success: true };
|
|
231
|
+
if (envSync.warning) {
|
|
232
|
+
result.envSyncWarning = envSync.warning;
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
204
235
|
} catch (err) {
|
|
205
236
|
throw new Error('Failed to restore settings: ' + err.message);
|
|
206
237
|
}
|
|
@@ -246,22 +277,27 @@ function setProxyConfig(proxyPort) {
|
|
|
246
277
|
// 直接设置 process.env 确保从本进程派生的 Codex CLI 能读到 CC_PROXY_KEY
|
|
247
278
|
process.env.CC_PROXY_KEY = 'PROXY_KEY';
|
|
248
279
|
|
|
249
|
-
const
|
|
280
|
+
const envSync = trySyncCodexUserEnvironment({
|
|
250
281
|
CC_PROXY_KEY: 'PROXY_KEY'
|
|
251
282
|
}, {
|
|
252
283
|
replace: false
|
|
253
284
|
});
|
|
285
|
+
const envResult = envSync.result;
|
|
254
286
|
|
|
255
287
|
console.log(`Codex settings updated to use proxy on port ${proxyPort}`);
|
|
256
|
-
|
|
288
|
+
const result = {
|
|
257
289
|
success: true,
|
|
258
290
|
port: proxyPort,
|
|
259
|
-
envInjected:
|
|
260
|
-
isFirstTime: envResult
|
|
261
|
-
shellConfigPath: envResult
|
|
262
|
-
sourceCommand: envResult
|
|
263
|
-
reloadRequired: envResult
|
|
291
|
+
envInjected: envSync.success,
|
|
292
|
+
isFirstTime: envResult?.isFirstTime || false,
|
|
293
|
+
shellConfigPath: envResult?.shellConfigPath || null,
|
|
294
|
+
sourceCommand: envResult?.sourceCommand || null,
|
|
295
|
+
reloadRequired: envResult?.reloadRequired || false
|
|
264
296
|
};
|
|
297
|
+
if (envSync.warning) {
|
|
298
|
+
result.envSyncWarning = envSync.warning;
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
265
301
|
} catch (err) {
|
|
266
302
|
throw new Error('Failed to set proxy config: ' + err.message);
|
|
267
303
|
}
|
|
@@ -335,4 +371,7 @@ module.exports = {
|
|
|
335
371
|
setProxyConfig,
|
|
336
372
|
isProxyConfig,
|
|
337
373
|
getCurrentProxyPort,
|
|
374
|
+
_test: {
|
|
375
|
+
isRecoverableEnvSyncError
|
|
376
|
+
}
|
|
338
377
|
};
|