aicodeswitch 3.9.3 → 4.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.
@@ -30,6 +30,8 @@ const websocket_service_1 = require("./websocket-service");
30
30
  const rules_status_service_1 = require("./rules-status-service");
31
31
  const type_migration_1 = require("./type-migration");
32
32
  const config_metadata_1 = require("./config-metadata");
33
+ const config_merge_1 = require("./config-merge");
34
+ const config_managed_fields_1 = require("./config-managed-fields");
33
35
  const config_1 = require("./config");
34
36
  const appDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch');
35
37
  const legacyDataDir = path_1.default.join(appDir, 'data');
@@ -104,7 +106,13 @@ const asyncHandler = (handler) => (req, res, next) => {
104
106
  next(err);
105
107
  });
106
108
  };
107
- const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
109
+ const VALID_CLAUDE_EFFORT_LEVELS = ['low', 'medium', 'high', 'max'];
110
+ const DEFAULT_CLAUDE_EFFORT_LEVEL = 'medium';
111
+ const isClaudeEffortLevel = (value) => {
112
+ return typeof value === 'string' && VALID_CLAUDE_EFFORT_LEVELS.includes(value);
113
+ };
114
+ const writeClaudeConfig = (dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, effortLevel_1, defaultModel_1, ...args_1) => __awaiter(void 0, [dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, effortLevel_1, defaultModel_1, ...args_1], void 0, function* (dbManager, enableAgentTeams, enableBypassPermissionsSupport, effortLevel, defaultModel, options = {}) {
115
+ var _a;
108
116
  try {
109
117
  const homeDir = os_1.default.homedir();
110
118
  const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
@@ -116,29 +124,44 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
116
124
  const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
117
125
  // 使用新的配置状态检测来判断是否可以写入
118
126
  const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
127
+ const isRuntimeRefresh = options.allowOverwriteRefresh === true && configStatus.isOverwritten;
119
128
  // 只有当当前配置已经是代理配置时,才拒绝写入
120
- if (configStatus.isOverwritten) {
129
+ if (configStatus.isOverwritten && !isRuntimeRefresh) {
121
130
  console.error('Claude config has already been overwritten. Please restore the original config first.');
122
131
  return false;
123
132
  }
124
133
  // 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
125
- let originalSettingsHash = undefined;
126
- if (!fs_1.default.existsSync(claudeSettingsBakPath)) {
127
- // 计算原始配置文件的 hash(如果存在)
128
- if (fs_1.default.existsSync(claudeSettingsPath)) {
129
- originalSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
130
- // 备份当前配置文件
131
- fs_1.default.renameSync(claudeSettingsPath, claudeSettingsBakPath);
134
+ let originalSettingsHash = isRuntimeRefresh
135
+ ? (_a = configStatus.metadata) === null || _a === void 0 ? void 0 : _a.originalHash
136
+ : undefined;
137
+ if (!isRuntimeRefresh) {
138
+ if (!fs_1.default.existsSync(claudeSettingsBakPath)) {
139
+ // 计算原始配置文件的 hash(如果存在)
140
+ if (fs_1.default.existsSync(claudeSettingsPath)) {
141
+ originalSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
142
+ // 备份当前配置文件
143
+ fs_1.default.renameSync(claudeSettingsPath, claudeSettingsBakPath);
144
+ }
145
+ }
146
+ else {
147
+ // .aicodeswitch_backup 已存在,直接使用现有的备份文件
148
+ console.log('Backup file already exists, skipping backup step');
132
149
  }
133
- }
134
- else {
135
- // .aicodeswitch_backup 已存在,直接使用现有的备份文件
136
- console.log('Backup file already exists, skipping backup step');
137
150
  }
138
151
  if (!fs_1.default.existsSync(claudeDir)) {
139
152
  fs_1.default.mkdirSync(claudeDir, { recursive: true });
140
153
  }
141
- // 构建环境变量配置
154
+ // 读取当前配置(如果存在),保留工具运行时写入的内容
155
+ let currentSettings = {};
156
+ if (fs_1.default.existsSync(claudeSettingsPath)) {
157
+ try {
158
+ currentSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'));
159
+ }
160
+ catch (error) {
161
+ console.warn('Failed to parse current settings.json, using empty object:', error);
162
+ }
163
+ }
164
+ // 构建代理配置
142
165
  const claudeSettingsEnv = {
143
166
  ANTHROPIC_AUTH_TOKEN: config.apiKey || "api_key",
144
167
  ANTHROPIC_API_KEY: "",
@@ -150,32 +173,56 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
150
173
  if (enableAgentTeams) {
151
174
  claudeSettingsEnv.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
152
175
  }
153
- const claudeSettings = {
176
+ const proxySettings = {
154
177
  env: claudeSettingsEnv
155
178
  };
156
179
  // 如果开启对bypassPermissions的支持,添加对应的配置项
157
180
  if (enableBypassPermissionsSupport) {
158
- claudeSettings.permissions = {
181
+ proxySettings.permissions = {
159
182
  defaultMode: "bypassPermissions"
160
183
  };
161
- claudeSettings.skipDangerousModePermissionPrompt = true;
184
+ proxySettings.skipDangerousModePermissionPrompt = true;
185
+ }
186
+ // 如果设置了 effortLevel,添加对应的配置项
187
+ if (effortLevel && isClaudeEffortLevel(effortLevel)) {
188
+ proxySettings.effortLevel = effortLevel;
189
+ }
190
+ // 如果设置了默认模型,添加对应的配置项
191
+ if (defaultModel && typeof defaultModel === 'string' && defaultModel.trim()) {
192
+ proxySettings.model = defaultModel.trim();
162
193
  }
163
- fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(claudeSettings, null, 2));
194
+ // 使用智能合并:将代理配置的管理字段写入,保留当前配置的非管理字段
195
+ const mergedSettings = (0, config_merge_1.mergeJsonConfig)(proxySettings, currentSettings, config_managed_fields_1.CLAUDE_SETTINGS_MANAGED_FIELDS);
196
+ // 原子性写入合并后的配置
197
+ (0, config_merge_1.atomicWriteFile)(claudeSettingsPath, JSON.stringify(mergedSettings, null, 2));
164
198
  // Claude Code .claude.json
165
199
  const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
166
- // 先读取原文件内容(如果存在)
167
- let claudeJson = {};
200
+ // 读取当前配置(如果存在),保留工具运行时写入的内容
201
+ let currentClaudeJson = {};
168
202
  if (fs_1.default.existsSync(claudeJsonPath)) {
169
- claudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf8'));
203
+ try {
204
+ currentClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf-8'));
205
+ }
206
+ catch (error) {
207
+ console.warn('Failed to parse current .claude.json, using empty object:', error);
208
+ }
170
209
  }
171
210
  // 然后处理备份
172
- if (!fs_1.default.existsSync(claudeJsonBakPath)) {
173
- if (fs_1.default.existsSync(claudeJsonPath)) {
174
- fs_1.default.renameSync(claudeJsonPath, claudeJsonBakPath);
211
+ if (!isRuntimeRefresh) {
212
+ if (!fs_1.default.existsSync(claudeJsonBakPath)) {
213
+ if (fs_1.default.existsSync(claudeJsonPath)) {
214
+ fs_1.default.renameSync(claudeJsonPath, claudeJsonBakPath);
215
+ }
175
216
  }
176
217
  }
177
- claudeJson.hasCompletedOnboarding = true;
178
- fs_1.default.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
218
+ // 构建代理配置
219
+ const proxyClaudeJson = {
220
+ hasCompletedOnboarding: true
221
+ };
222
+ // 使用智能合并
223
+ const mergedClaudeJson = (0, config_merge_1.mergeJsonConfig)(proxyClaudeJson, currentClaudeJson, config_managed_fields_1.CLAUDE_JSON_MANAGED_FIELDS);
224
+ // 原子性写入合并后的配置
225
+ (0, config_merge_1.atomicWriteFile)(claudeJsonPath, JSON.stringify(mergedClaudeJson, null, 2));
179
226
  // 保存元数据
180
227
  const currentSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
181
228
  const metadata = {
@@ -203,125 +250,13 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
203
250
  return false;
204
251
  }
205
252
  });
206
- /**
207
- * 更新Claude Code配置中的Agent Teams设置
208
- * 此函数假设配置文件已经被代理覆盖,直接修改环境变量而不重新备份
209
- */
210
- const updateClaudeAgentTeamsConfig = (enableAgentTeams) => __awaiter(void 0, void 0, void 0, function* () {
211
- try {
212
- const homeDir = os_1.default.homedir();
213
- const claudeSettingsPath = path_1.default.join(homeDir, '.claude/settings.json');
214
- // 检查配置文件是否存在
215
- if (!fs_1.default.existsSync(claudeSettingsPath)) {
216
- console.error('Claude settings.json does not exist');
217
- return false;
218
- }
219
- // 读取当前配置
220
- const currentContent = fs_1.default.readFileSync(claudeSettingsPath, 'utf-8');
221
- const currentConfig = JSON.parse(currentContent);
222
- // 检查是否是代理配置
223
- const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
224
- if (!configStatus.isOverwritten) {
225
- console.error('Claude config is not overwritten by proxy. Please activate a route first.');
226
- return false;
227
- }
228
- // 更新或删除Agent Teams环境变量
229
- if (enableAgentTeams) {
230
- currentConfig.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
231
- }
232
- else {
233
- delete currentConfig.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
234
- }
235
- // 写入更新后的配置
236
- fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(currentConfig, null, 2));
237
- // 更新元数据中的当前配置hash
238
- const metadata = (0, config_metadata_1.loadMetadata)('claude');
239
- if (metadata && metadata.files[0]) {
240
- metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
241
- .update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'))
242
- .digest('hex');
243
- metadata.timestamp = Date.now();
244
- (0, config_metadata_1.saveMetadata)(metadata);
245
- }
246
- return true;
247
- }
248
- catch (error) {
249
- console.error('Failed to update Claude Agent Teams config:', error);
250
- return false;
251
- }
252
- });
253
- /**
254
- * 更新Claude Code配置中的bypassPermissions支持设置
255
- * 此函数假设配置文件已经被代理覆盖,直接修改配置而不重新备份
256
- */
257
- const updateClaudeBypassPermissionsSupportConfig = (enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
258
- try {
259
- const homeDir = os_1.default.homedir();
260
- const claudeSettingsPath = path_1.default.join(homeDir, '.claude/settings.json');
261
- // 检查配置文件是否存在
262
- if (!fs_1.default.existsSync(claudeSettingsPath)) {
263
- console.error('Claude settings.json does not exist');
264
- return false;
265
- }
266
- // 读取当前配置
267
- const currentContent = fs_1.default.readFileSync(claudeSettingsPath, 'utf-8');
268
- const currentConfig = JSON.parse(currentContent);
269
- // 检查是否是代理配置
270
- const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
271
- if (!configStatus.isOverwritten) {
272
- console.error('Claude config is not overwritten by proxy. Please activate a route first.');
273
- return false;
274
- }
275
- // 更新或删除bypassPermissions支持配置项
276
- if (enableBypassPermissionsSupport) {
277
- currentConfig.permissions = {
278
- defaultMode: "bypassPermissions"
279
- };
280
- currentConfig.skipDangerousModePermissionPrompt = true;
281
- }
282
- else {
283
- delete currentConfig.permissions;
284
- delete currentConfig.skipDangerousModePermissionPrompt;
285
- }
286
- // 写入更新后的配置
287
- fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(currentConfig, null, 2));
288
- // 更新元数据中的当前配置hash
289
- const metadata = (0, config_metadata_1.loadMetadata)('claude');
290
- if (metadata && metadata.files[0]) {
291
- metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
292
- .update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'))
293
- .digest('hex');
294
- metadata.timestamp = Date.now();
295
- (0, config_metadata_1.saveMetadata)(metadata);
296
- }
297
- return true;
298
- }
299
- catch (error) {
300
- console.error('Failed to update Claude bypassPermissions support config:', error);
301
- return false;
302
- }
303
- });
304
- const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high'];
253
+ const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
305
254
  const DEFAULT_CODEX_REASONING_EFFORT = 'high';
306
255
  const isCodexReasoningEffort = (value) => {
307
256
  return typeof value === 'string' && VALID_CODEX_REASONING_EFFORTS.includes(value);
308
257
  };
309
- const buildCodexConfigToml = (modelReasoningEffort) => {
310
- const localPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
311
- return `model_provider = "aicodeswitch"
312
- model = "gpt-5.1-codex"
313
- model_reasoning_effort = "${modelReasoningEffort}"
314
- disable_response_storage = true
315
-
316
-
317
- [model_providers.aicodeswitch]
318
- name = "aicodeswitch"
319
- base_url = "http://${host}:${localPort}/codex"
320
- wire_api = "responses"
321
- requires_openai_auth = true
322
- `;
323
- };
324
- const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManager_1, ...args_1], void 0, function* (dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT) {
258
+ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManager_1, ...args_1], void 0, function* (dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT, codexDefaultModel, options = {}) {
259
+ var _a;
325
260
  try {
326
261
  const homeDir = os_1.default.homedir();
327
262
  const config = dbManager.getConfig();
@@ -332,41 +267,92 @@ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManage
332
267
  const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
333
268
  // 使用新的配置状态检测来判断是否可以写入
334
269
  const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
270
+ const isRuntimeRefresh = options.allowOverwriteRefresh === true && configStatus.isOverwritten;
335
271
  // 只有当当前配置已经是代理配置时,才拒绝写入
336
- if (configStatus.isOverwritten) {
272
+ if (configStatus.isOverwritten && !isRuntimeRefresh) {
337
273
  console.error('Codex config has already been overwritten. Please restore the original config first.');
338
274
  return false;
339
275
  }
340
276
  // 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
341
- let originalConfigHash = undefined;
342
- if (!fs_1.default.existsSync(codexConfigBakPath)) {
343
- // 计算原始配置文件的 hash(如果存在)
344
- if (fs_1.default.existsSync(codexConfigPath)) {
345
- originalConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
346
- // 备份当前配置文件
347
- fs_1.default.renameSync(codexConfigPath, codexConfigBakPath);
277
+ let originalConfigHash = isRuntimeRefresh
278
+ ? (_a = configStatus.metadata) === null || _a === void 0 ? void 0 : _a.originalHash
279
+ : undefined;
280
+ if (!isRuntimeRefresh) {
281
+ if (!fs_1.default.existsSync(codexConfigBakPath)) {
282
+ // 计算原始配置文件的 hash(如果存在)
283
+ if (fs_1.default.existsSync(codexConfigPath)) {
284
+ originalConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
285
+ // 备份当前配置文件
286
+ fs_1.default.renameSync(codexConfigPath, codexConfigBakPath);
287
+ }
288
+ }
289
+ else {
290
+ // .aicodeswitch_backup 已存在,直接使用现有的备份文件
291
+ console.log('Backup file already exists, skipping backup step');
348
292
  }
349
- }
350
- else {
351
- // .aicodeswitch_backup 已存在,直接使用现有的备份文件
352
- console.log('Backup file already exists, skipping backup step');
353
293
  }
354
294
  if (!fs_1.default.existsSync(codexDir)) {
355
295
  fs_1.default.mkdirSync(codexDir, { recursive: true });
356
296
  }
357
- fs_1.default.writeFileSync(codexConfigPath, buildCodexConfigToml(modelReasoningEffort));
297
+ // 读取当前配置(如果存在),保留工具运行时写入的内容
298
+ let currentConfig = {};
299
+ if (fs_1.default.existsSync(codexConfigPath)) {
300
+ try {
301
+ currentConfig = (0, config_merge_1.parseToml)(fs_1.default.readFileSync(codexConfigPath, 'utf-8'));
302
+ }
303
+ catch (error) {
304
+ console.warn('Failed to parse current config.toml, using empty object:', error);
305
+ }
306
+ }
307
+ // 构建代理配置
308
+ const proxyConfig = {
309
+ model_provider: "aicodeswitch",
310
+ model: codexDefaultModel || "gpt-5.3-codex", // 使用配置的默认模型,否则使用默认值
311
+ model_reasoning_effort: modelReasoningEffort,
312
+ disable_response_storage: true,
313
+ preferred_auth_method: "apikey",
314
+ requires_openai_auth: true,
315
+ enableRouteSelection: true,
316
+ model_providers: {
317
+ aicodeswitch: {
318
+ name: "aicodeswitch",
319
+ base_url: `http://${host}:${process.env.PORT ? parseInt(process.env.PORT, 10) : 4567}/codex`,
320
+ wire_api: "responses"
321
+ }
322
+ }
323
+ };
324
+ // 使用智能合并
325
+ const mergedConfig = (0, config_merge_1.mergeTomlConfig)(proxyConfig, currentConfig, config_managed_fields_1.CODEX_CONFIG_MANAGED_FIELDS);
326
+ // 原子性写入合并后的配置
327
+ (0, config_merge_1.atomicWriteFile)(codexConfigPath, (0, config_merge_1.stringifyToml)(mergedConfig));
358
328
  // Codex auth.json
359
329
  const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
330
+ // 读取当前配置(如果存在),保留工具运行时写入的内容
331
+ let currentAuth = {};
332
+ if (fs_1.default.existsSync(codexAuthPath)) {
333
+ try {
334
+ currentAuth = JSON.parse(fs_1.default.readFileSync(codexAuthPath, 'utf-8'));
335
+ }
336
+ catch (error) {
337
+ console.warn('Failed to parse current auth.json, using empty object:', error);
338
+ }
339
+ }
360
340
  // 同样处理 auth.json 的备份
361
- if (!fs_1.default.existsSync(codexAuthBakPath)) {
362
- if (fs_1.default.existsSync(codexAuthPath)) {
363
- fs_1.default.renameSync(codexAuthPath, codexAuthBakPath);
341
+ if (!isRuntimeRefresh) {
342
+ if (!fs_1.default.existsSync(codexAuthBakPath)) {
343
+ if (fs_1.default.existsSync(codexAuthPath)) {
344
+ fs_1.default.renameSync(codexAuthPath, codexAuthBakPath);
345
+ }
364
346
  }
365
347
  }
366
- const codexAuth = {
348
+ // 构建代理配置
349
+ const proxyAuth = {
367
350
  OPENAI_API_KEY: config.apiKey || "api_key"
368
351
  };
369
- fs_1.default.writeFileSync(codexAuthPath, JSON.stringify(codexAuth, null, 2));
352
+ // 使用智能合并
353
+ const mergedAuth = (0, config_merge_1.mergeJsonConfig)(proxyAuth, currentAuth, config_managed_fields_1.CODEX_AUTH_MANAGED_FIELDS);
354
+ // 原子性写入合并后的配置
355
+ (0, config_merge_1.atomicWriteFile)(codexAuthPath, JSON.stringify(mergedAuth, null, 2));
370
356
  // 保存元数据
371
357
  const currentConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
372
358
  const metadata = {
@@ -394,60 +380,62 @@ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManage
394
380
  return false;
395
381
  }
396
382
  });
397
- const updateCodexReasoningEffortConfig = (modelReasoningEffort) => __awaiter(void 0, void 0, void 0, function* () {
398
- try {
399
- const homeDir = os_1.default.homedir();
400
- const codexConfigPath = path_1.default.join(homeDir, '.codex/config.toml');
401
- if (!fs_1.default.existsSync(codexConfigPath)) {
402
- console.error('Codex config.toml does not exist');
403
- return false;
404
- }
405
- const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
406
- if (!configStatus.isOverwritten) {
407
- console.error('Codex config is not overwritten by proxy. Please activate a route first.');
408
- return false;
409
- }
410
- fs_1.default.writeFileSync(codexConfigPath, buildCodexConfigToml(modelReasoningEffort));
411
- const metadata = (0, config_metadata_1.loadMetadata)('codex');
412
- if (metadata && metadata.files[0]) {
413
- metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
414
- .update(fs_1.default.readFileSync(codexConfigPath, 'utf-8'))
415
- .digest('hex');
416
- metadata.timestamp = Date.now();
417
- (0, config_metadata_1.saveMetadata)(metadata);
418
- }
419
- return true;
420
- }
421
- catch (error) {
422
- console.error('Failed to update Codex reasoning effort config:', error);
423
- return false;
424
- }
425
- });
426
383
  const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* () {
427
384
  try {
428
385
  const homeDir = os_1.default.homedir();
386
+ let restoredAnyFile = false;
429
387
  // Restore Claude Code settings.json
430
388
  const claudeDir = path_1.default.join(homeDir, '.claude');
431
389
  const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
432
390
  const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.aicodeswitch_backup');
433
391
  if (fs_1.default.existsSync(claudeSettingsBakPath)) {
392
+ // 读取备份配置
393
+ const backupSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsBakPath, 'utf-8'));
394
+ // 读取当前配置(可能包含工具运行时写入的新内容)
395
+ let currentSettings = {};
434
396
  if (fs_1.default.existsSync(claudeSettingsPath)) {
435
- fs_1.default.unlinkSync(claudeSettingsPath);
397
+ try {
398
+ currentSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'));
399
+ }
400
+ catch (error) {
401
+ console.warn('Failed to parse current settings.json during restore, using empty object:', error);
402
+ }
436
403
  }
437
- fs_1.default.renameSync(claudeSettingsBakPath, claudeSettingsPath);
404
+ // 生成合并后的配置(备份作为基础,合并当前的非管理字段)
405
+ const mergedSettings = (0, config_merge_1.mergeJsonConfig)(backupSettings, currentSettings, config_managed_fields_1.CLAUDE_SETTINGS_MANAGED_FIELDS);
406
+ // 原子性写入合并后的配置
407
+ (0, config_merge_1.atomicWriteFile)(claudeSettingsPath, JSON.stringify(mergedSettings, null, 2));
408
+ // 删除备份文件
409
+ fs_1.default.unlinkSync(claudeSettingsBakPath);
410
+ restoredAnyFile = true;
438
411
  }
439
412
  // Restore Claude Code .claude.json
440
413
  const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
441
414
  const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
442
415
  if (fs_1.default.existsSync(claudeJsonBakPath)) {
416
+ // 读取备份配置
417
+ const backupClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonBakPath, 'utf-8'));
418
+ // 读取当前配置(可能包含工具运行时写入的新内容)
419
+ let currentClaudeJson = {};
443
420
  if (fs_1.default.existsSync(claudeJsonPath)) {
444
- fs_1.default.unlinkSync(claudeJsonPath);
421
+ try {
422
+ currentClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf-8'));
423
+ }
424
+ catch (error) {
425
+ console.warn('Failed to parse current .claude.json during restore, using empty object:', error);
426
+ }
445
427
  }
446
- fs_1.default.renameSync(claudeJsonBakPath, claudeJsonPath);
428
+ // 生成合并后的配置
429
+ const mergedClaudeJson = (0, config_merge_1.mergeJsonConfig)(backupClaudeJson, currentClaudeJson, config_managed_fields_1.CLAUDE_JSON_MANAGED_FIELDS);
430
+ // 原子性写入合并后的配置
431
+ (0, config_merge_1.atomicWriteFile)(claudeJsonPath, JSON.stringify(mergedClaudeJson, null, 2));
432
+ // 删除备份文件
433
+ fs_1.default.unlinkSync(claudeJsonBakPath);
434
+ restoredAnyFile = true;
447
435
  }
448
436
  // 删除元数据
449
437
  (0, config_metadata_1.deleteMetadata)('claude');
450
- return true;
438
+ return restoredAnyFile;
451
439
  }
452
440
  catch (error) {
453
441
  console.error('Failed to restore Claude config files:', error);
@@ -457,28 +445,59 @@ const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* ()
457
445
  const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* () {
458
446
  try {
459
447
  const homeDir = os_1.default.homedir();
448
+ let restoredAnyFile = false;
460
449
  // Restore Codex config.toml
461
450
  const codexDir = path_1.default.join(homeDir, '.codex');
462
451
  const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
463
452
  const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.aicodeswitch_backup');
464
453
  if (fs_1.default.existsSync(codexConfigBakPath)) {
454
+ // 读取备份配置
455
+ const backupConfig = (0, config_merge_1.parseToml)(fs_1.default.readFileSync(codexConfigBakPath, 'utf-8'));
456
+ // 读取当前配置(可能包含工具运行时写入的新内容)
457
+ let currentConfig = {};
465
458
  if (fs_1.default.existsSync(codexConfigPath)) {
466
- fs_1.default.unlinkSync(codexConfigPath);
459
+ try {
460
+ currentConfig = (0, config_merge_1.parseToml)(fs_1.default.readFileSync(codexConfigPath, 'utf-8'));
461
+ }
462
+ catch (error) {
463
+ console.warn('Failed to parse current config.toml during restore, using empty object:', error);
464
+ }
467
465
  }
468
- fs_1.default.renameSync(codexConfigBakPath, codexConfigPath);
466
+ // 生成合并后的配置(备份作为基础,合并当前的非管理字段)
467
+ const mergedConfig = (0, config_merge_1.mergeTomlConfig)(backupConfig, currentConfig, config_managed_fields_1.CODEX_CONFIG_MANAGED_FIELDS);
468
+ // 原子性写入合并后的配置
469
+ (0, config_merge_1.atomicWriteFile)(codexConfigPath, (0, config_merge_1.stringifyToml)(mergedConfig));
470
+ // 删除备份文件
471
+ fs_1.default.unlinkSync(codexConfigBakPath);
472
+ restoredAnyFile = true;
469
473
  }
470
474
  // Restore Codex auth.json
471
475
  const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
472
476
  const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
473
477
  if (fs_1.default.existsSync(codexAuthBakPath)) {
478
+ // 读取备份配置
479
+ const backupAuth = JSON.parse(fs_1.default.readFileSync(codexAuthBakPath, 'utf-8'));
480
+ // 读取当前配置(可能包含工具运行时写入的新内容)
481
+ let currentAuth = {};
474
482
  if (fs_1.default.existsSync(codexAuthPath)) {
475
- fs_1.default.unlinkSync(codexAuthPath);
483
+ try {
484
+ currentAuth = JSON.parse(fs_1.default.readFileSync(codexAuthPath, 'utf-8'));
485
+ }
486
+ catch (error) {
487
+ console.warn('Failed to parse current auth.json during restore, using empty object:', error);
488
+ }
476
489
  }
477
- fs_1.default.renameSync(codexAuthBakPath, codexAuthPath);
490
+ // 生成合并后的配置
491
+ const mergedAuth = (0, config_merge_1.mergeJsonConfig)(backupAuth, currentAuth, config_managed_fields_1.CODEX_AUTH_MANAGED_FIELDS);
492
+ // 原子性写入合并后的配置
493
+ (0, config_merge_1.atomicWriteFile)(codexAuthPath, JSON.stringify(mergedAuth, null, 2));
494
+ // 删除备份文件
495
+ fs_1.default.unlinkSync(codexAuthBakPath);
496
+ restoredAnyFile = true;
478
497
  }
479
498
  // 删除元数据
480
499
  (0, config_metadata_1.deleteMetadata)('codex');
481
- return true;
500
+ return restoredAnyFile;
482
501
  }
483
502
  catch (error) {
484
503
  console.error('Failed to restore Codex config files:', error);
@@ -513,6 +532,33 @@ const checkCodexBackupExists = () => {
513
532
  return false;
514
533
  }
515
534
  };
535
+ const syncConfigsOnServerStartup = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
536
+ const config = dbManager.getConfig();
537
+ // 服务启动即执行写入,参数来源改为全局配置
538
+ const claudeEffortLevel = isClaudeEffortLevel(config.claudeEffortLevel)
539
+ ? config.claudeEffortLevel
540
+ : DEFAULT_CLAUDE_EFFORT_LEVEL;
541
+ const claudeWritten = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, claudeEffortLevel, config.claudeDefaultModel);
542
+ console.log(`[Startup Config Sync] Claude Code config ${claudeWritten ? 'written' : 'skipped'}`);
543
+ const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
544
+ ? config.codexModelReasoningEffort
545
+ : DEFAULT_CODEX_REASONING_EFFORT;
546
+ const codexWritten = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel);
547
+ console.log(`[Startup Config Sync] Codex config ${codexWritten ? 'written' : 'skipped'}`);
548
+ });
549
+ const syncConfigsOnGlobalConfigUpdate = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
550
+ const config = dbManager.getConfig();
551
+ const claudeEffortLevel = isClaudeEffortLevel(config.claudeEffortLevel)
552
+ ? config.claudeEffortLevel
553
+ : DEFAULT_CLAUDE_EFFORT_LEVEL;
554
+ const claudeUpdated = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, claudeEffortLevel, config.claudeDefaultModel, { allowOverwriteRefresh: true });
555
+ console.log(`[Config Update Sync] Claude Code config ${claudeUpdated ? 'written' : 'skipped'}`);
556
+ const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
557
+ ? config.codexModelReasoningEffort
558
+ : DEFAULT_CODEX_REASONING_EFFORT;
559
+ const codexUpdated = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, { allowOverwriteRefresh: true });
560
+ console.log(`[Config Update Sync] Codex config ${codexUpdated ? 'written' : 'skipped'}`);
561
+ });
516
562
  const getCentralSkillsDir = () => {
517
563
  return path_1.default.join(os_1.default.homedir(), '.aicodeswitch', 'skills');
518
564
  };
@@ -902,8 +948,12 @@ const registerRoutes = (dbManager, proxyServer) => {
902
948
  }
903
949
  });
904
950
  app.get('/api/vendors', (_req, res) => res.json(dbManager.getVendors()));
905
- app.post('/api/vendors', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () { return res.json(yield dbManager.createVendor(req.body)); })));
906
- app.put('/api/vendors/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () { return res.json(yield dbManager.updateVendor(req.params.id, req.body)); })));
951
+ app.post('/api/vendors', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
952
+ res.json(yield dbManager.createVendor(req.body));
953
+ })));
954
+ app.put('/api/vendors/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
955
+ res.json(yield dbManager.updateVendor(req.params.id, req.body));
956
+ })));
907
957
  app.delete('/api/vendors/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
908
958
  try {
909
959
  const result = yield dbManager.deleteVendor(req.params.id);
@@ -925,7 +975,14 @@ const registerRoutes = (dbManager, proxyServer) => {
925
975
  console.log('[创建服务] 创建结果:', JSON.stringify(result, null, 2));
926
976
  res.json(result);
927
977
  })));
928
- app.put('/api/services/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () { return res.json(yield dbManager.updateAPIService(req.params.id, req.body)); })));
978
+ app.put('/api/services/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
979
+ const existingService = dbManager.getAPIService(req.params.id);
980
+ if (!existingService) {
981
+ res.status(404).json({ error: '服务不存在' });
982
+ return;
983
+ }
984
+ res.json(yield dbManager.updateAPIService(req.params.id, req.body));
985
+ })));
929
986
  app.delete('/api/services/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
930
987
  console.log('[删除服务] 请求 ID:', req.params.id);
931
988
  try {
@@ -970,26 +1027,8 @@ const registerRoutes = (dbManager, proxyServer) => {
970
1027
  })));
971
1028
  // 批量停用所有激活的路由(用于应用关闭时清理)
972
1029
  app.post('/api/routes/deactivate-all', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
973
- console.log('[Deactivate All Routes] Starting cleanup process...');
974
- // 步骤1:恢复 Claude Code 配置文件
975
- try {
976
- console.log('[Deactivate All Routes] Restoring Claude Code config...');
977
- const claudeRestored = yield restoreClaudeConfig();
978
- console.log(`[Deactivate All Routes] Claude Code config ${claudeRestored ? 'restored' : 'was not modified'}`);
979
- }
980
- catch (error) {
981
- console.error('[Deactivate All Routes] Failed to restore Claude config:', error);
982
- }
983
- // 步骤2:恢复 Codex 配置文件
984
- try {
985
- console.log('[Deactivate All Routes] Restoring Codex config...');
986
- const codexRestored = yield restoreCodexConfig();
987
- console.log(`[Deactivate All Routes] Codex config ${codexRestored ? 'restored' : 'was not modified'}`);
988
- }
989
- catch (error) {
990
- console.error('[Deactivate All Routes] Failed to restore Codex config:', error);
991
- }
992
- // 步骤3:停用所有激活的路由
1030
+ console.log('[Deactivate All Routes] Starting route deactivation...');
1031
+ // 仅停用路由,不再在路由接口内处理配置文件恢复
993
1032
  console.log('[Deactivate All Routes] Deactivating all active routes...');
994
1033
  const deactivatedCount = yield dbManager.deactivateAllRoutes();
995
1034
  if (deactivatedCount > 0) {
@@ -1000,7 +1039,7 @@ const registerRoutes = (dbManager, proxyServer) => {
1000
1039
  else {
1001
1040
  console.log('[Deactivate All Routes] No active routes to deactivate');
1002
1041
  }
1003
- console.log('[Deactivate All Routes] Cleanup process completed');
1042
+ console.log('[Deactivate All Routes] Route deactivation completed');
1004
1043
  res.json({
1005
1044
  success: true,
1006
1045
  deactivatedCount
@@ -1043,6 +1082,28 @@ const registerRoutes = (dbManager, proxyServer) => {
1043
1082
  res.status(500).json({ error: 'Failed to clear blacklist' });
1044
1083
  }
1045
1084
  })));
1085
+ // 清除规则的错误状态(广播 idle 状态给所有客户端)
1086
+ app.post('/api/rules/:id/clear-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1087
+ const { id } = req.params;
1088
+ const rule = dbManager.getRule(id);
1089
+ if (!rule) {
1090
+ res.status(404).json({ error: 'Rule not found' });
1091
+ return;
1092
+ }
1093
+ // 找到该规则所属的路由
1094
+ const routes = dbManager.getRoutes();
1095
+ const route = routes.find(r => {
1096
+ const rules = dbManager.getRules(r.id);
1097
+ return rules.some(r => r.id === id);
1098
+ });
1099
+ if (!route) {
1100
+ res.status(404).json({ error: 'Route not found' });
1101
+ return;
1102
+ }
1103
+ // 广播 idle 状态给所有客户端
1104
+ rules_status_service_1.rulesStatusBroadcaster.markRuleIdle(route.id, id);
1105
+ res.json({ success: true });
1106
+ })));
1046
1107
  // 获取规则的黑名单状态
1047
1108
  app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1048
1109
  const { routeId } = req.params;
@@ -1132,8 +1193,10 @@ const registerRoutes = (dbManager, proxyServer) => {
1132
1193
  const config = req.body;
1133
1194
  const result = yield dbManager.updateConfig(config);
1134
1195
  if (result) {
1135
- yield proxyServer.updateConfig(config);
1136
- updateProxyConfig(config);
1196
+ const latestConfig = dbManager.getConfig();
1197
+ yield proxyServer.updateConfig(latestConfig);
1198
+ updateProxyConfig(latestConfig);
1199
+ yield syncConfigsOnGlobalConfigUpdate(dbManager);
1137
1200
  }
1138
1201
  res.json(result);
1139
1202
  })));
@@ -1474,29 +1537,53 @@ ${instruction}
1474
1537
  res.json({ success: true });
1475
1538
  })));
1476
1539
  app.post('/api/write-config/claude', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1477
- const enableAgentTeams = req.body.enableAgentTeams;
1478
- const enableBypassPermissionsSupport = req.body.enableBypassPermissionsSupport;
1479
- const result = yield writeClaudeConfig(dbManager, enableAgentTeams, enableBypassPermissionsSupport);
1540
+ const appConfig = dbManager.getConfig();
1541
+ const requestedEnableAgentTeams = req.body.enableAgentTeams;
1542
+ const requestedBypass = req.body.enableBypassPermissionsSupport;
1543
+ const enableAgentTeams = typeof requestedEnableAgentTeams === 'boolean'
1544
+ ? requestedEnableAgentTeams
1545
+ : appConfig.enableAgentTeams;
1546
+ const enableBypassPermissionsSupport = typeof requestedBypass === 'boolean'
1547
+ ? requestedBypass
1548
+ : appConfig.enableBypassPermissionsSupport;
1549
+ const result = yield writeClaudeConfig(dbManager, enableAgentTeams, enableBypassPermissionsSupport, undefined, appConfig.claudeDefaultModel);
1480
1550
  res.json(result);
1481
1551
  })));
1482
1552
  app.post('/api/write-config/codex', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1553
+ const appConfig = dbManager.getConfig();
1483
1554
  const requestedEffort = req.body.modelReasoningEffort;
1484
1555
  const modelReasoningEffort = isCodexReasoningEffort(requestedEffort)
1485
1556
  ? requestedEffort
1486
- : DEFAULT_CODEX_REASONING_EFFORT;
1487
- const result = yield writeCodexConfig(dbManager, modelReasoningEffort);
1557
+ : isCodexReasoningEffort(appConfig.codexModelReasoningEffort)
1558
+ ? appConfig.codexModelReasoningEffort
1559
+ : DEFAULT_CODEX_REASONING_EFFORT;
1560
+ const result = yield writeCodexConfig(dbManager, modelReasoningEffort, appConfig.codexDefaultModel);
1488
1561
  res.json(result);
1489
1562
  })));
1490
- // 更新Claude Code配置中的Agent Teams设置(当路由已激活时)
1563
+ // 兼容接口:更新全局 Agent Teams 配置
1491
1564
  app.post('/api/update-claude-agent-teams', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1492
1565
  const { enableAgentTeams } = req.body;
1493
- const result = yield updateClaudeAgentTeamsConfig(enableAgentTeams);
1566
+ const current = dbManager.getConfig();
1567
+ const result = yield dbManager.updateConfig(Object.assign(Object.assign({}, current), { enableAgentTeams: !!enableAgentTeams }));
1568
+ if (result) {
1569
+ const latestConfig = dbManager.getConfig();
1570
+ yield proxyServer.updateConfig(latestConfig);
1571
+ updateProxyConfig(latestConfig);
1572
+ yield syncConfigsOnGlobalConfigUpdate(dbManager);
1573
+ }
1494
1574
  res.json(result);
1495
1575
  })));
1496
- // 更新Claude Code配置中的bypassPermissions支持设置(当路由已激活时)
1576
+ // 兼容接口:更新全局 bypassPermissions 支持配置
1497
1577
  app.post('/api/update-claude-bypass-permissions-support', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1498
1578
  const { enableBypassPermissionsSupport } = req.body;
1499
- const result = yield updateClaudeBypassPermissionsSupportConfig(enableBypassPermissionsSupport);
1579
+ const current = dbManager.getConfig();
1580
+ const result = yield dbManager.updateConfig(Object.assign(Object.assign({}, current), { enableBypassPermissionsSupport: !!enableBypassPermissionsSupport }));
1581
+ if (result) {
1582
+ const latestConfig = dbManager.getConfig();
1583
+ yield proxyServer.updateConfig(latestConfig);
1584
+ updateProxyConfig(latestConfig);
1585
+ yield syncConfigsOnGlobalConfigUpdate(dbManager);
1586
+ }
1500
1587
  res.json(result);
1501
1588
  })));
1502
1589
  app.post('/api/update-codex-reasoning-effort', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -1505,7 +1592,14 @@ ${instruction}
1505
1592
  res.status(400).json({ error: 'Invalid modelReasoningEffort' });
1506
1593
  return;
1507
1594
  }
1508
- const result = yield updateCodexReasoningEffortConfig(requestedEffort);
1595
+ const current = dbManager.getConfig();
1596
+ const result = yield dbManager.updateConfig(Object.assign(Object.assign({}, current), { codexModelReasoningEffort: requestedEffort }));
1597
+ if (result) {
1598
+ const latestConfig = dbManager.getConfig();
1599
+ yield proxyServer.updateConfig(latestConfig);
1600
+ updateProxyConfig(latestConfig);
1601
+ yield syncConfigsOnGlobalConfigUpdate(dbManager);
1602
+ }
1509
1603
  res.json(result);
1510
1604
  })));
1511
1605
  app.post('/api/restore-config/claude', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -1815,6 +1909,15 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1815
1909
  console.log('[Server] Initializing database...');
1816
1910
  const dbManager = yield database_factory_1.DatabaseFactory.createAuto(dataDir, legacyDataDir);
1817
1911
  console.log('[Server] Database initialized successfully');
1912
+ // 服务启动时自动同步配置文件(适用于 CLI 和 dev:server)
1913
+ console.log('[Server] Syncing tool configs with global settings...');
1914
+ try {
1915
+ yield syncConfigsOnServerStartup(dbManager);
1916
+ console.log('[Server] Tool config sync completed');
1917
+ }
1918
+ catch (error) {
1919
+ console.error('[Server] Tool config sync failed:', error);
1920
+ }
1818
1921
  const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
1819
1922
  // Initialize proxy server and register proxy routes last
1820
1923
  proxyServer.initialize();
@@ -1845,6 +1948,12 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1845
1948
  const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
1846
1949
  // 创建 WebSocket 服务器用于规则状态
1847
1950
  const rulesStatusWss = (0, rules_status_service_1.createRulesStatusWSServer)();
1951
+ // 设置黑名单检查函数,用于在规则状态同步时检查黑名单是否已过期
1952
+ rules_status_service_1.rulesStatusBroadcaster.setBlacklistChecker((serviceId, routeId, contentType) => __awaiter(void 0, void 0, void 0, function* () {
1953
+ // 检查服务��否在黑名单中
1954
+ const isBlacklisted = yield dbManager.isServiceBlacklisted(serviceId, routeId, contentType);
1955
+ return isBlacklisted;
1956
+ }));
1848
1957
  // 将 WebSocket 服务器附加到 HTTP 服务器
1849
1958
  server.on('upgrade', (request, socket, head) => {
1850
1959
  if (request.url === '/api/tools/install') {
@@ -1863,16 +1972,46 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1863
1972
  });
1864
1973
  console.log(`WebSocket server for tool installation attached to ws://${host}:${port}/api/tools/install`);
1865
1974
  console.log(`WebSocket server for rules status attached to ws://${host}:${port}/api/rules/status`);
1866
- const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
1867
- console.log('Shutting down server...');
1868
- dbManager.close();
1869
- server.close(() => {
1975
+ let isShuttingDown = false;
1976
+ let shutdownPromise = null;
1977
+ const shutdown = (signal) => __awaiter(void 0, void 0, void 0, function* () {
1978
+ if (isShuttingDown) {
1979
+ return shutdownPromise !== null && shutdownPromise !== void 0 ? shutdownPromise : Promise.resolve();
1980
+ }
1981
+ isShuttingDown = true;
1982
+ shutdownPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
1983
+ console.log(`[Server] Received ${signal}, shutting down...`);
1984
+ // 服务终止前恢复配置文件(适用于 aicos stop 与 Ctrl+C)
1985
+ try {
1986
+ const claudeRestored = yield restoreClaudeConfig();
1987
+ console.log(`[Shutdown ...] Claude Code config ${claudeRestored ? 'restored' : 'was not modified'}`);
1988
+ }
1989
+ catch (error) {
1990
+ console.error('[Shutdown ...] Failed to restore Claude config:', error);
1991
+ }
1992
+ try {
1993
+ const codexRestored = yield restoreCodexConfig();
1994
+ console.log(`[Shutdown ...] Codex config ${codexRestored ? 'restored' : 'was not modified'}`);
1995
+ }
1996
+ catch (error) {
1997
+ console.error('[Shutdown ...] Failed to restore Codex config:', error);
1998
+ }
1999
+ dbManager.close();
2000
+ yield Promise.race([
2001
+ new Promise((resolve) => {
2002
+ server.close(() => resolve());
2003
+ }),
2004
+ new Promise((resolve) => {
2005
+ setTimeout(resolve, 5000);
2006
+ })
2007
+ ]);
1870
2008
  console.log('Server stopped.');
1871
2009
  process.exit(0);
1872
- });
2010
+ }))();
2011
+ return shutdownPromise;
1873
2012
  });
1874
- process.on('SIGINT', shutdown);
1875
- process.on('SIGTERM', shutdown);
2013
+ process.on('SIGINT', () => { void shutdown('SIGINT'); });
2014
+ process.on('SIGTERM', () => { void shutdown('SIGTERM'); });
1876
2015
  });
1877
2016
  // 全局未捕获异常处理 - 防止服务崩溃
1878
2017
  process.on('uncaughtException', (error) => {