aicodeswitch 5.2.13 → 6.0.0

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.
@@ -4,8 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>AI Code Switch</title>
7
- <script type="module" crossorigin src="./assets/index-CJMAVkIN.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-4liE4bV8.css">
7
+ <script type="module" crossorigin src="./assets/index-QZBX5HHX.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/three-CFpmPosW.js">
9
+ <link rel="stylesheet" crossorigin href="./assets/index-CQVhBlBB.css">
9
10
  </head>
10
11
  <body>
11
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicodeswitch",
3
- "version": "5.2.13",
3
+ "version": "6.0.0",
4
4
  "description": "A tool to help you manage AI programming tools to access large language models locally. It allows your Claude Code, Codex and other tools to no longer be limited to official models.",
5
5
  "author": "tangshuang",
6
6
  "license": "GPL-3.0",
@@ -80,6 +80,7 @@
80
80
  "@types/node": "^20.11.5",
81
81
  "@types/react": "^18.2.43",
82
82
  "@types/react-dom": "^18.2.17",
83
+ "@types/three": "^0.165.0",
83
84
  "@typescript-eslint/eslint-plugin": "^6.14.0",
84
85
  "@typescript-eslint/parser": "^6.14.0",
85
86
  "@vitejs/plugin-react": "^4.2.1",
@@ -96,6 +97,7 @@
96
97
  "react-router-dom": "^6.21.3",
97
98
  "recharts": "^3.7.0",
98
99
  "standard-version": "^9.5.0",
100
+ "three": "^0.165.0",
99
101
  "tsx": "^4.7.0",
100
102
  "typescript": "^5.2.2",
101
103
  "vite": "^5.0.8"
package/scripts/dev.js CHANGED
@@ -104,43 +104,87 @@ function prefixStream(child, label) {
104
104
  }
105
105
  }
106
106
 
107
+ const IS_WIN = process.platform === 'win32';
108
+
107
109
  /**
108
- * 等待子进程 exit(触发于 process.exit 或被信号杀死)。
109
- * @returns {Promise<number>} exit code signal 字符串
110
+ * 进程组是否仍存活(组内还有任意进程)。
111
+ * 子进程以 detached:true 启动后,其 pid == pgid,故 -pid 可探测整组。
112
+ * 关键点:tsx 会 fork——serverChild 只是 tsx 启动器(launcher),真正运行
113
+ * main.ts 的 node 进程、以及 esbuild 服务进程都是它的子孙,与启动器同组。
114
+ * 启动器的 'exit'/'close' 在它自身一退出就触发(Ctrl+C 时几乎立刻),
115
+ * 而真正的服务进程仍在做数秒优雅关闭并随后打印 "Server stopped."。
116
+ * 因此「等启动器退出」≠「等服务停止」。改用「轮询整组存活」:只要组内
117
+ * 还有任何进程(真正的 node 服务),就算未停止;等整组清空才等价于
118
+ * 「Server stopped. 已出现并 process.exit」。
110
119
  */
111
- function waitForExit(child) {
112
- return new Promise((resolve) => {
113
- if (child.exitCode !== null || child.signalCode) {
114
- resolve(child.exitCode ?? 0);
115
- return;
116
- }
117
- child.once('exit', (code, signal) => {
118
- resolve(code ?? (signal ? 128 + 1 : 0));
119
- });
120
- });
120
+ function groupAlive(pid) {
121
+ if (pid == null) return false;
122
+ try {
123
+ process.kill(-pid, 0); // 信号 0 = 探测存在性;-pid = 整个进程组
124
+ return true;
125
+ } catch {
126
+ return false;
127
+ }
121
128
  }
122
129
 
123
- function safeKill(child, signal) {
130
+ /**
131
+ * 向子进程所在进程组发信号(覆盖 launcher + 真正服务 + esbuild)。
132
+ * Windows 无进程组语义,退化为直接 kill 直接子进程。
133
+ */
134
+ function signalGroup(child, signal) {
135
+ if (!child || child.exitCode !== null || child.signalCode) return;
136
+ const pid = child.pid;
137
+ if (IS_WIN || pid == null) {
138
+ try { child.kill(signal); } catch { /* ignore */ }
139
+ return;
140
+ }
124
141
  try {
125
- if (child && child.exitCode === null && !child.signalCode) {
126
- child.kill(signal);
127
- }
142
+ process.kill(-pid, signal);
128
143
  } catch {
129
- /* ignore */
144
+ try { child.kill(signal); } catch { /* ignore */ }
130
145
  }
131
146
  }
132
147
 
148
+ /**
149
+ * 等待整个进程组清空(POSIX),Windows 退化为等待直接子进程 'close'。
150
+ * 不带内部超时——超时由调用方用 SIGKILL 清组后,本函数下一拍(≤100ms)即感知并 resolve。
151
+ * @returns {Promise<number>} 直接子进程的 exit code(仅用于异常码上报)
152
+ */
153
+ function waitForGroupExit(child) {
154
+ return new Promise((resolve) => {
155
+ const pid = child.pid;
156
+ if (IS_WIN || pid == null) {
157
+ if (child.exitCode !== null || child.signalCode) { resolve(child.exitCode ?? 0); return; }
158
+ child.once('close', (code, signal) => resolve(code ?? (signal ? 128 + 1 : 0)));
159
+ return;
160
+ }
161
+ if (!groupAlive(pid)) { resolve(child.exitCode ?? 0); return; }
162
+ const iv = setInterval(() => {
163
+ if (!groupAlive(pid)) {
164
+ clearInterval(iv);
165
+ resolve(child.exitCode ?? 0);
166
+ }
167
+ }, 100);
168
+ });
169
+ }
170
+
133
171
  async function main() {
134
172
  let exitCode = 0;
135
173
  let shuttingDown = false;
136
174
  let uiChild = null;
137
175
 
138
- // 直接调本地 bin,去掉 `npm run` 额外进程层
139
- // shell:true Windows 能找到 .cmd bin
176
+ // 直接调本地 bin,去掉 `npm run` 额外进程层。
177
+ // POSIX 不开 shell(避免中间 /bin/sh 抢先于 SIGINT 退出);Windows .cmd 仍需 shell。
178
+ // detached:true:把子进程放进它自己的进程组(POSIX setsid),这样我们可以用
179
+ // process.kill(-pid, sig) 给整组发信号、用 groupAlive(-pid) 探测整组存活。
180
+ // 这一点对本脚本的核心目标至关重要——见 waitForGroupExit 注释:tsx 会 fork,
181
+ // serverChild 只是启动器,真正的 node 服务是它的孙进程,只有「整组存活探测」
182
+ // 才能把退出阻塞到真正的 "Server stopped."。
183
+ const USE_SHELL = IS_WIN;
140
184
  const serverChild = spawn(
141
185
  'tsx',
142
186
  ['--tsconfig=tsconfig.server.json', 'src/server/main.ts'],
143
- { cwd: ROOT, shell: true, stdio: ['inherit', 'pipe', 'pipe'] },
187
+ { cwd: ROOT, shell: USE_SHELL, detached: !IS_WIN, stdio: ['inherit', 'pipe', 'pipe'] },
144
188
  );
145
189
  prefixStream(serverChild, SERVER_LABEL);
146
190
 
@@ -173,10 +217,11 @@ async function main() {
173
217
  }
174
218
  process.stdout.write(`${DEV_LABEL} 服务端已就绪,启动 UI...\n`);
175
219
 
176
- // 2) 启动 UI
220
+ // 2) 启动 UI(同样放进独立进程组,便于整组信号/清理,避免 vite 的 esbuild 子进程残留)
177
221
  uiChild = spawn('vite', [], {
178
222
  cwd: ROOT,
179
- shell: true,
223
+ shell: USE_SHELL,
224
+ detached: !IS_WIN,
180
225
  stdio: ['inherit', 'pipe', 'pipe'],
181
226
  });
182
227
  prefixStream(uiChild, UI_LABEL);
@@ -205,9 +250,10 @@ async function main() {
205
250
  );
206
251
  }
207
252
 
208
- // 子进程随进程组已直接收到信号,这里再显式转发一次(幂等,无副作用)
209
- safeKill(serverChild, 'SIGINT');
210
- safeKill(uiChild, 'SIGINT');
253
+ // 子进程各自在独立进程组里,Ctrl+C 不会直达它们,必须由父进程显式给整组发 SIGINT,
254
+ // 真正的服务进程才会进入优雅关闭(恢复配置 + server.close + 打印 "Server stopped.")。
255
+ signalGroup(serverChild, 'SIGINT');
256
+ signalGroup(uiChild, 'SIGINT');
211
257
 
212
258
  let timedOut = false;
213
259
  const timer = setTimeout(() => {
@@ -215,13 +261,13 @@ async function main() {
215
261
  process.stderr.write(
216
262
  `${DEV_LABEL} 等待超时(${SHUTDOWN_TIMEOUT_MS / 1000}s),强制终止残留子进程。\n`,
217
263
  );
218
- safeKill(serverChild, 'SIGKILL');
219
- safeKill(uiChild, 'SIGKILL');
264
+ signalGroup(serverChild, 'SIGKILL');
265
+ signalGroup(uiChild, 'SIGKILL');
220
266
  }, SHUTDOWN_TIMEOUT_MS);
221
267
 
222
- // 服务是长时序(配置恢复 + server.close 最多 5s),UI 通常已先退出
223
- const exits = [waitForExit(serverChild)];
224
- if (uiChild) exits.push(waitForExit(uiChild));
268
+ // 阻塞到「整组清空」:等真正的 node 服务进程也退出(而不仅是 tsx 启动器)。
269
+ const exits = [waitForGroupExit(serverChild)];
270
+ if (uiChild) exits.push(waitForGroupExit(uiChild));
225
271
  const [serverCode, uiCode] = await Promise.all(exits);
226
272
 
227
273
  clearTimeout(timer);
@@ -1,203 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getToolsInstallationStatus = getToolsInstallationStatus;
16
- exports.installTool = installTool;
17
- const child_process_1 = require("child_process");
18
- const os_1 = __importDefault(require("os"));
19
- /**
20
- * 检测工具是否已安装
21
- */
22
- function checkToolInstalled(toolName) {
23
- return __awaiter(this, void 0, void 0, function* () {
24
- console.log(`[ToolsService] 开始检测工具: ${toolName}`);
25
- return new Promise((resolve) => {
26
- var _a, _b;
27
- const command = toolName === 'claude-code' ? 'claude' : 'codex';
28
- console.log(`[ToolsService] 执行命令: ${command} --version`);
29
- const child = (0, child_process_1.spawn)(command, ['--version'], {
30
- shell: true,
31
- stdio: ['ignore', 'pipe', 'pipe'],
32
- windowsHide: true, // 隐藏 Windows 命令行窗口,避免检测时闪窗
33
- });
34
- let stdout = '';
35
- let stderr = '';
36
- (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
37
- stdout += data.toString();
38
- console.log(`[ToolsService] ${toolName} stdout:`, data.toString());
39
- });
40
- (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
41
- stderr += data.toString();
42
- console.log(`[ToolsService] ${toolName} stderr:`, data.toString());
43
- });
44
- child.on('close', (code) => {
45
- console.log(`[ToolsService] ${toolName} 进程退出,退出码: ${code}`);
46
- if (code === 0) {
47
- // 尝试从输出中提取版本号
48
- const versionMatch = stdout.match(/(\d+\.\d+\.\d+)/) || stderr.match(/(\d+\.\d+\.\d+)/);
49
- resolve({
50
- installed: true,
51
- version: versionMatch ? versionMatch[1] : 'unknown',
52
- });
53
- }
54
- else {
55
- resolve({ installed: false });
56
- }
57
- });
58
- child.on('error', (err) => {
59
- console.log(`[ToolsService] ${toolName} 检测出错:`, err);
60
- resolve({ installed: false });
61
- });
62
- // 10秒超时
63
- setTimeout(() => {
64
- console.log(`[ToolsService] ${toolName} 检测超时`);
65
- child.kill();
66
- resolve({ installed: false });
67
- }, 10000);
68
- });
69
- });
70
- }
71
- /**
72
- * 获取工具的安装命令
73
- */
74
- function getInstallCommand(toolName) {
75
- const platform = os_1.default.platform();
76
- const tool = toolName === 'claude-code' ? '@anthropic-ai/claude-code' : '@openai/codex';
77
- if (platform === 'win32') {
78
- return `npm install -g ${tool}`;
79
- }
80
- else {
81
- // macOS 和 Linux 需要 sudo
82
- return `sudo npm install -g ${tool}`;
83
- }
84
- }
85
- /**
86
- * 检测所有工具的安装状态
87
- */
88
- function getToolsInstallationStatus() {
89
- return __awaiter(this, void 0, void 0, function* () {
90
- const [claudeCodeStatus, codexStatus] = yield Promise.all([
91
- checkToolInstalled('claude-code'),
92
- checkToolInstalled('codex'),
93
- ]);
94
- return {
95
- claudeCode: Object.assign(Object.assign({}, claudeCodeStatus), { installCommand: claudeCodeStatus.installed ? undefined : getInstallCommand('claude-code') }),
96
- codex: Object.assign(Object.assign({}, codexStatus), { installCommand: codexStatus.installed ? undefined : getInstallCommand('codex') }),
97
- };
98
- });
99
- }
100
- /**
101
- * 执行工具安装
102
- */
103
- function installTool(toolName, callbacks) {
104
- var _a, _b;
105
- const command = getInstallCommand(toolName);
106
- const platform = os_1.default.platform();
107
- console.log(`[ToolsService] ========== 开始安装 ${toolName} ==========`);
108
- console.log(`[ToolsService] 操作系统: ${platform}`);
109
- console.log(`[ToolsService] 安装命令: ${command}`);
110
- // 立即发送启动消息,让前端快速进入 terminal 界面
111
- callbacks.onData(`\n========== 开始安装 ${toolName} ==========\n`);
112
- callbacks.onData(`操作系统: ${platform}\n`);
113
- callbacks.onData(`执行命令: ${command}\n`);
114
- callbacks.onData(`进程启动中...\n\n`);
115
- // 在 macOS/Linux 上,需要使用 sudo 并且可能需要输入密码
116
- // 在 Windows 上直接使用 cmd 执行 npm 命令
117
- let child;
118
- if (platform === 'win32') {
119
- // Windows: 使用 cmd.exe 执行命令
120
- console.log(`[ToolsService] Windows 环境,使用 cmd.exe`);
121
- child = (0, child_process_1.spawn)('cmd.exe', ['/c', command], {
122
- stdio: ['pipe', 'pipe', 'pipe'],
123
- shell: false,
124
- windowsHide: true, // 隐藏命令行窗口
125
- });
126
- }
127
- else {
128
- // Unix: 使用 sh 执行命令
129
- console.log(`[ToolsService] Unix 环境,使用 sh`);
130
- child = (0, child_process_1.spawn)('sh', ['-c', command], {
131
- stdio: ['pipe', 'pipe', 'pipe'],
132
- shell: false,
133
- });
134
- }
135
- console.log(`[ToolsService] 子进程 PID: ${child.pid}`);
136
- // 立即发送进程信息
137
- callbacks.onData(`子进程已创建 (PID: ${child.pid})\n`);
138
- callbacks.onData(`等待 npm 输出...\n\n`);
139
- // 设置超时:30分钟后自动终止
140
- const timeout = 30 * 60 * 1000;
141
- const timeoutHandle = setTimeout(() => {
142
- console.error(`[ToolsService] ${toolName} 安装超时 (${timeout}ms),终止进程`);
143
- child.kill('SIGTERM');
144
- callbacks.onError(`\n安装超时 (${timeout / 60000} 分钟),请检查网络连接或手动执行命令:\n${command}\n`);
145
- }, timeout);
146
- let hasReceivedData = false;
147
- (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
148
- const output = data.toString();
149
- if (!hasReceivedData) {
150
- console.log(`[ToolsService] ${toolName} 首次收到 stdout 数据`);
151
- hasReceivedData = true;
152
- }
153
- console.log(`[ToolsService] ${toolName} stdout:`, output.slice(0, 200)); // 只记录前200字符
154
- callbacks.onData(output);
155
- });
156
- (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
157
- const output = data.toString();
158
- if (!hasReceivedData) {
159
- console.log(`[ToolsService] ${toolName} 首次收到 stderr 数据`);
160
- hasReceivedData = true;
161
- }
162
- console.log(`[ToolsService] ${toolName} stderr:`, output.slice(0, 200)); // 只记录前200字符
163
- callbacks.onError(output);
164
- });
165
- child.on('close', (code) => {
166
- clearTimeout(timeoutHandle);
167
- console.log(`[ToolsService] ${toolName} 安装进程关闭,退出码: ${code}`);
168
- if (code === 0) {
169
- callbacks.onData(`\n========== 安装完成 ==========\n`);
170
- }
171
- else if (code === null) {
172
- callbacks.onError(`\n========== 安装被终止 ==========\n`);
173
- }
174
- else {
175
- callbacks.onError(`\n========== 安装失败 (退出码: ${code}) ==========\n`);
176
- }
177
- callbacks.onClose(code);
178
- });
179
- child.on('error', (err) => {
180
- clearTimeout(timeoutHandle);
181
- console.error(`[ToolsService] ${toolName} 安装进程错误:`, err);
182
- callbacks.onError(`启动安装进程失败: ${err.message}\n`);
183
- callbacks.onError(`错误详情: ${err}\n`);
184
- callbacks.onClose(null);
185
- });
186
- // 处理进程退出信号
187
- child.on('exit', (_code, signal) => {
188
- clearTimeout(timeoutHandle);
189
- if (signal) {
190
- console.log(`[ToolsService] ${toolName} 进程被信号终止: ${signal}`);
191
- callbacks.onError(`\n安装进程被信号终止: ${signal}\n`);
192
- }
193
- });
194
- // 如果10秒后还没有收到任何数据,发送提示
195
- setTimeout(() => {
196
- if (!hasReceivedData) {
197
- console.log(`[ToolsService] ${toolName} 10秒内未收到输出,发送等待提示`);
198
- callbacks.onData(`[提示] 正在连接 npm 服务器,这可能需要一些时间...\n`);
199
- callbacks.onData(`[提示] 如果持续无响应,请检查网络连接或 npm 配置\n\n`);
200
- }
201
- }, 10000);
202
- return child;
203
- }
@@ -1,148 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ToolInstallationWS = void 0;
7
- exports.createToolInstallationWSServer = createToolInstallationWSServer;
8
- // @ts-ignore - ws 类型声明可能需要手动安装 @types/ws
9
- const ws_1 = require("ws");
10
- const tools_service_1 = require("./tools-service");
11
- const os_1 = __importDefault(require("os"));
12
- /**
13
- * WebSocket 连接管理
14
- */
15
- class ToolInstallationWS {
16
- constructor(ws, req) {
17
- Object.defineProperty(this, "ws", {
18
- enumerable: true,
19
- configurable: true,
20
- writable: true,
21
- value: void 0
22
- });
23
- Object.defineProperty(this, "childProcess", {
24
- enumerable: true,
25
- configurable: true,
26
- writable: true,
27
- value: null
28
- });
29
- this.ws = ws;
30
- console.log(`[WS] 新的 WebSocket 连接: ${req.socket.remoteAddress}`);
31
- this.ws.on('message', (data) => {
32
- try {
33
- const message = JSON.parse(data.toString());
34
- console.log(`[WS] 收到消息:`, message.type);
35
- if (message.type === 'input') {
36
- // 用户输入(如密码),发送到子进程的 stdin
37
- if (this.childProcess && this.childProcess.stdin) {
38
- console.log(`[WS] 发送用户输入到子进程:`, message.data.slice(0, 10));
39
- this.childProcess.stdin.write(message.data);
40
- }
41
- else {
42
- console.warn(`[WS] 子进程不存在或无 stdin,无法发送输入`);
43
- }
44
- }
45
- }
46
- catch (err) {
47
- console.error(`[WS] 解析消息失败:`, err);
48
- }
49
- });
50
- this.ws.on('close', () => {
51
- console.log(`[WS] WebSocket 连接关闭`);
52
- this.cleanup();
53
- });
54
- this.ws.on('error', (err) => {
55
- console.error(`[WS] WebSocket 错误:`, err);
56
- this.cleanup();
57
- });
58
- }
59
- /**
60
- * 开始安装工具
61
- */
62
- startInstallation(toolName) {
63
- console.log(`[WS] 开始安装 ${toolName}`);
64
- const callbacks = {
65
- onData: (data) => {
66
- this.sendMessage({ type: 'stdout', data });
67
- },
68
- onError: (data) => {
69
- this.sendMessage({ type: 'stderr', data });
70
- },
71
- onClose: (code) => {
72
- const success = code === 0;
73
- this.sendMessage({ type: 'close', data: { code, success } });
74
- // 延迟关闭连接,让客户端收到最终消息
75
- setTimeout(() => {
76
- this.ws.close();
77
- }, 1000);
78
- },
79
- };
80
- // 启动安装进程
81
- this.childProcess = (0, tools_service_1.installTool)(toolName, callbacks);
82
- // 发送启动信息
83
- const platform = os_1.default.platform();
84
- const command = platform === 'win32'
85
- ? `npm install -g ${toolName === 'claude-code' ? '@anthropic-ai/claude-code' : '@openai/codex'}`
86
- : `sudo npm install -g ${toolName === 'claude-code' ? '@anthropic-ai/claude-code' : '@openai/codex'}`;
87
- this.sendMessage({
88
- type: 'start',
89
- data: {
90
- tool: toolName,
91
- os: platform,
92
- command,
93
- pid: this.childProcess.pid || 0,
94
- },
95
- });
96
- }
97
- /**
98
- * 发送消息到客户端
99
- */
100
- sendMessage(message) {
101
- if (this.ws.readyState === ws_1.WebSocket.OPEN) {
102
- this.ws.send(JSON.stringify(message));
103
- }
104
- }
105
- /**
106
- * 清理资源
107
- */
108
- cleanup() {
109
- if (this.childProcess) {
110
- console.log(`[WS] 清理资源,终止子进程`);
111
- // 给进程5秒时间正常退出
112
- setTimeout(() => {
113
- if (this.childProcess && !this.childProcess.killed) {
114
- this.childProcess.kill('SIGTERM');
115
- }
116
- }, 5000);
117
- }
118
- }
119
- }
120
- exports.ToolInstallationWS = ToolInstallationWS;
121
- /**
122
- * 创建 WebSocket 服务器
123
- */
124
- function createToolInstallationWSServer() {
125
- const wss = new ws_1.WebSocketServer({ noServer: true });
126
- wss.on('connection', (ws, req) => {
127
- const wsHandler = new ToolInstallationWS(ws, req);
128
- // 监听第一次消息,获取要安装的工具名称
129
- ws.once('message', (data) => {
130
- try {
131
- const message = JSON.parse(data.toString());
132
- if (message.type === 'install' && message.tool) {
133
- wsHandler.startInstallation(message.tool);
134
- }
135
- else {
136
- ws.send(JSON.stringify({ type: 'error', data: '无效的请求' }));
137
- ws.close();
138
- }
139
- }
140
- catch (error) {
141
- console.error(`[WS] 解析安装请求失败:`, error);
142
- ws.send(JSON.stringify({ type: 'error', data: '解析请求失败' }));
143
- ws.close();
144
- }
145
- });
146
- });
147
- return wss;
148
- }