aicodeswitch 3.9.4 → 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,32 +106,13 @@ const asyncHandler = (handler) => (req, res, next) => {
104
106
  next(err);
105
107
  });
106
108
  };
107
- const OPENAI_V1_SUFFIX_RE = /\/v1\/?$/i;
108
- const validateOpenAIServiceBaseUrl = (service) => {
109
- if ((service === null || service === void 0 ? void 0 : service.sourceType) !== 'openai') {
110
- return null;
111
- }
112
- if (typeof service.apiUrl !== 'string') {
113
- return null;
114
- }
115
- if (!OPENAI_V1_SUFFIX_RE.test(service.apiUrl.trim())) {
116
- return null;
117
- }
118
- return 'OpenAI 数据源请填写不包含 /v1 的 base URL,例如:https://api.openai.com';
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);
119
113
  };
120
- const validateOpenAIServiceBaseUrlsInVendorPayload = (vendorBody) => {
121
- if (!Array.isArray(vendorBody === null || vendorBody === void 0 ? void 0 : vendorBody.services)) {
122
- return null;
123
- }
124
- for (const service of vendorBody.services) {
125
- const error = validateOpenAIServiceBaseUrl(service);
126
- if (error) {
127
- return error;
128
- }
129
- }
130
- return null;
131
- };
132
- const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
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;
133
116
  try {
134
117
  const homeDir = os_1.default.homedir();
135
118
  const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
@@ -141,29 +124,44 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
141
124
  const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
142
125
  // 使用新的配置状态检测来判断是否可以写入
143
126
  const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
127
+ const isRuntimeRefresh = options.allowOverwriteRefresh === true && configStatus.isOverwritten;
144
128
  // 只有当当前配置已经是代理配置时,才拒绝写入
145
- if (configStatus.isOverwritten) {
129
+ if (configStatus.isOverwritten && !isRuntimeRefresh) {
146
130
  console.error('Claude config has already been overwritten. Please restore the original config first.');
147
131
  return false;
148
132
  }
149
133
  // 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
150
- let originalSettingsHash = undefined;
151
- if (!fs_1.default.existsSync(claudeSettingsBakPath)) {
152
- // 计算原始配置文件的 hash(如果存在)
153
- if (fs_1.default.existsSync(claudeSettingsPath)) {
154
- originalSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
155
- // 备份当前配置文件
156
- 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');
157
149
  }
158
- }
159
- else {
160
- // .aicodeswitch_backup 已存在,直接使用现有的备份文件
161
- console.log('Backup file already exists, skipping backup step');
162
150
  }
163
151
  if (!fs_1.default.existsSync(claudeDir)) {
164
152
  fs_1.default.mkdirSync(claudeDir, { recursive: true });
165
153
  }
166
- // 构建环境变量配置
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
+ // 构建代理配置
167
165
  const claudeSettingsEnv = {
168
166
  ANTHROPIC_AUTH_TOKEN: config.apiKey || "api_key",
169
167
  ANTHROPIC_API_KEY: "",
@@ -175,32 +173,56 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
175
173
  if (enableAgentTeams) {
176
174
  claudeSettingsEnv.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
177
175
  }
178
- const claudeSettings = {
176
+ const proxySettings = {
179
177
  env: claudeSettingsEnv
180
178
  };
181
179
  // 如果开启对bypassPermissions的支持,添加对应的配置项
182
180
  if (enableBypassPermissionsSupport) {
183
- claudeSettings.permissions = {
181
+ proxySettings.permissions = {
184
182
  defaultMode: "bypassPermissions"
185
183
  };
186
- claudeSettings.skipDangerousModePermissionPrompt = true;
184
+ proxySettings.skipDangerousModePermissionPrompt = true;
185
+ }
186
+ // 如果设置了 effortLevel,添加对应的配置项
187
+ if (effortLevel && isClaudeEffortLevel(effortLevel)) {
188
+ proxySettings.effortLevel = effortLevel;
187
189
  }
188
- fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(claudeSettings, null, 2));
190
+ // 如果设置了默认模型,添加对应的配置项
191
+ if (defaultModel && typeof defaultModel === 'string' && defaultModel.trim()) {
192
+ proxySettings.model = defaultModel.trim();
193
+ }
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));
189
198
  // Claude Code .claude.json
190
199
  const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
191
- // 先读取原文件内容(如果存在)
192
- let claudeJson = {};
200
+ // 读取当前配置(如果存在),保留工具运行时写入的内容
201
+ let currentClaudeJson = {};
193
202
  if (fs_1.default.existsSync(claudeJsonPath)) {
194
- 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
+ }
195
209
  }
196
210
  // 然后处理备份
197
- if (!fs_1.default.existsSync(claudeJsonBakPath)) {
198
- if (fs_1.default.existsSync(claudeJsonPath)) {
199
- 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
+ }
200
216
  }
201
217
  }
202
- claudeJson.hasCompletedOnboarding = true;
203
- 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));
204
226
  // 保存元数据
205
227
  const currentSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
206
228
  const metadata = {
@@ -228,125 +250,13 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
228
250
  return false;
229
251
  }
230
252
  });
231
- /**
232
- * 更新Claude Code配置中的Agent Teams设置
233
- * 此函数假设配置文件已经被代理覆盖,直接修改环境变量而不重新备份
234
- */
235
- const updateClaudeAgentTeamsConfig = (enableAgentTeams) => __awaiter(void 0, void 0, void 0, function* () {
236
- try {
237
- const homeDir = os_1.default.homedir();
238
- const claudeSettingsPath = path_1.default.join(homeDir, '.claude/settings.json');
239
- // 检查配置文件是否存在
240
- if (!fs_1.default.existsSync(claudeSettingsPath)) {
241
- console.error('Claude settings.json does not exist');
242
- return false;
243
- }
244
- // 读取当前配置
245
- const currentContent = fs_1.default.readFileSync(claudeSettingsPath, 'utf-8');
246
- const currentConfig = JSON.parse(currentContent);
247
- // 检查是否是代理配置
248
- const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
249
- if (!configStatus.isOverwritten) {
250
- console.error('Claude config is not overwritten by proxy. Please activate a route first.');
251
- return false;
252
- }
253
- // 更新或删除Agent Teams环境变量
254
- if (enableAgentTeams) {
255
- currentConfig.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
256
- }
257
- else {
258
- delete currentConfig.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
259
- }
260
- // 写入更新后的配置
261
- fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(currentConfig, null, 2));
262
- // 更新元数据中的当前配置hash
263
- const metadata = (0, config_metadata_1.loadMetadata)('claude');
264
- if (metadata && metadata.files[0]) {
265
- metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
266
- .update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'))
267
- .digest('hex');
268
- metadata.timestamp = Date.now();
269
- (0, config_metadata_1.saveMetadata)(metadata);
270
- }
271
- return true;
272
- }
273
- catch (error) {
274
- console.error('Failed to update Claude Agent Teams config:', error);
275
- return false;
276
- }
277
- });
278
- /**
279
- * 更新Claude Code配置中的bypassPermissions支持设置
280
- * 此函数假设配置文件已经被代理覆盖,直接修改配置而不重新备份
281
- */
282
- const updateClaudeBypassPermissionsSupportConfig = (enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
283
- try {
284
- const homeDir = os_1.default.homedir();
285
- const claudeSettingsPath = path_1.default.join(homeDir, '.claude/settings.json');
286
- // 检查配置文件是否存在
287
- if (!fs_1.default.existsSync(claudeSettingsPath)) {
288
- console.error('Claude settings.json does not exist');
289
- return false;
290
- }
291
- // 读取当前配置
292
- const currentContent = fs_1.default.readFileSync(claudeSettingsPath, 'utf-8');
293
- const currentConfig = JSON.parse(currentContent);
294
- // 检查是否是代理配置
295
- const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
296
- if (!configStatus.isOverwritten) {
297
- console.error('Claude config is not overwritten by proxy. Please activate a route first.');
298
- return false;
299
- }
300
- // 更新或删除bypassPermissions支持配置项
301
- if (enableBypassPermissionsSupport) {
302
- currentConfig.permissions = {
303
- defaultMode: "bypassPermissions"
304
- };
305
- currentConfig.skipDangerousModePermissionPrompt = true;
306
- }
307
- else {
308
- delete currentConfig.permissions;
309
- delete currentConfig.skipDangerousModePermissionPrompt;
310
- }
311
- // 写入更新后的配置
312
- fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(currentConfig, null, 2));
313
- // 更新元数据中的当前配置hash
314
- const metadata = (0, config_metadata_1.loadMetadata)('claude');
315
- if (metadata && metadata.files[0]) {
316
- metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
317
- .update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'))
318
- .digest('hex');
319
- metadata.timestamp = Date.now();
320
- (0, config_metadata_1.saveMetadata)(metadata);
321
- }
322
- return true;
323
- }
324
- catch (error) {
325
- console.error('Failed to update Claude bypassPermissions support config:', error);
326
- return false;
327
- }
328
- });
329
- const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high'];
253
+ const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
330
254
  const DEFAULT_CODEX_REASONING_EFFORT = 'high';
331
255
  const isCodexReasoningEffort = (value) => {
332
256
  return typeof value === 'string' && VALID_CODEX_REASONING_EFFORTS.includes(value);
333
257
  };
334
- const buildCodexConfigToml = (modelReasoningEffort) => {
335
- const localPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
336
- return `model_provider = "aicodeswitch"
337
- model = "gpt-5.1-codex"
338
- model_reasoning_effort = "${modelReasoningEffort}"
339
- disable_response_storage = true
340
-
341
-
342
- [model_providers.aicodeswitch]
343
- name = "aicodeswitch"
344
- base_url = "http://${host}:${localPort}/codex"
345
- wire_api = "responses"
346
- requires_openai_auth = true
347
- `;
348
- };
349
- 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;
350
260
  try {
351
261
  const homeDir = os_1.default.homedir();
352
262
  const config = dbManager.getConfig();
@@ -357,41 +267,92 @@ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManage
357
267
  const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
358
268
  // 使用新的配置状态检测来判断是否可以写入
359
269
  const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
270
+ const isRuntimeRefresh = options.allowOverwriteRefresh === true && configStatus.isOverwritten;
360
271
  // 只有当当前配置已经是代理配置时,才拒绝写入
361
- if (configStatus.isOverwritten) {
272
+ if (configStatus.isOverwritten && !isRuntimeRefresh) {
362
273
  console.error('Codex config has already been overwritten. Please restore the original config first.');
363
274
  return false;
364
275
  }
365
276
  // 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
366
- let originalConfigHash = undefined;
367
- if (!fs_1.default.existsSync(codexConfigBakPath)) {
368
- // 计算原始配置文件的 hash(如果存在)
369
- if (fs_1.default.existsSync(codexConfigPath)) {
370
- originalConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
371
- // 备份当前配置文件
372
- 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');
373
292
  }
374
- }
375
- else {
376
- // .aicodeswitch_backup 已存在,直接使用现有的备份文件
377
- console.log('Backup file already exists, skipping backup step');
378
293
  }
379
294
  if (!fs_1.default.existsSync(codexDir)) {
380
295
  fs_1.default.mkdirSync(codexDir, { recursive: true });
381
296
  }
382
- 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));
383
328
  // Codex auth.json
384
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
+ }
385
340
  // 同样处理 auth.json 的备份
386
- if (!fs_1.default.existsSync(codexAuthBakPath)) {
387
- if (fs_1.default.existsSync(codexAuthPath)) {
388
- 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
+ }
389
346
  }
390
347
  }
391
- const codexAuth = {
348
+ // 构建代理配置
349
+ const proxyAuth = {
392
350
  OPENAI_API_KEY: config.apiKey || "api_key"
393
351
  };
394
- 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));
395
356
  // 保存元数据
396
357
  const currentConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
397
358
  const metadata = {
@@ -419,60 +380,62 @@ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManage
419
380
  return false;
420
381
  }
421
382
  });
422
- const updateCodexReasoningEffortConfig = (modelReasoningEffort) => __awaiter(void 0, void 0, void 0, function* () {
423
- try {
424
- const homeDir = os_1.default.homedir();
425
- const codexConfigPath = path_1.default.join(homeDir, '.codex/config.toml');
426
- if (!fs_1.default.existsSync(codexConfigPath)) {
427
- console.error('Codex config.toml does not exist');
428
- return false;
429
- }
430
- const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
431
- if (!configStatus.isOverwritten) {
432
- console.error('Codex config is not overwritten by proxy. Please activate a route first.');
433
- return false;
434
- }
435
- fs_1.default.writeFileSync(codexConfigPath, buildCodexConfigToml(modelReasoningEffort));
436
- const metadata = (0, config_metadata_1.loadMetadata)('codex');
437
- if (metadata && metadata.files[0]) {
438
- metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
439
- .update(fs_1.default.readFileSync(codexConfigPath, 'utf-8'))
440
- .digest('hex');
441
- metadata.timestamp = Date.now();
442
- (0, config_metadata_1.saveMetadata)(metadata);
443
- }
444
- return true;
445
- }
446
- catch (error) {
447
- console.error('Failed to update Codex reasoning effort config:', error);
448
- return false;
449
- }
450
- });
451
383
  const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* () {
452
384
  try {
453
385
  const homeDir = os_1.default.homedir();
386
+ let restoredAnyFile = false;
454
387
  // Restore Claude Code settings.json
455
388
  const claudeDir = path_1.default.join(homeDir, '.claude');
456
389
  const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
457
390
  const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.aicodeswitch_backup');
458
391
  if (fs_1.default.existsSync(claudeSettingsBakPath)) {
392
+ // 读取备份配置
393
+ const backupSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsBakPath, 'utf-8'));
394
+ // 读取当前配置(可能包含工具运行时写入的新内容)
395
+ let currentSettings = {};
459
396
  if (fs_1.default.existsSync(claudeSettingsPath)) {
460
- 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
+ }
461
403
  }
462
- 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;
463
411
  }
464
412
  // Restore Claude Code .claude.json
465
413
  const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
466
414
  const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
467
415
  if (fs_1.default.existsSync(claudeJsonBakPath)) {
416
+ // 读取备份配置
417
+ const backupClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonBakPath, 'utf-8'));
418
+ // 读取当前配置(可能包含工具运行时写入的新内容)
419
+ let currentClaudeJson = {};
468
420
  if (fs_1.default.existsSync(claudeJsonPath)) {
469
- 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
+ }
470
427
  }
471
- 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;
472
435
  }
473
436
  // 删除元数据
474
437
  (0, config_metadata_1.deleteMetadata)('claude');
475
- return true;
438
+ return restoredAnyFile;
476
439
  }
477
440
  catch (error) {
478
441
  console.error('Failed to restore Claude config files:', error);
@@ -482,28 +445,59 @@ const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* ()
482
445
  const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* () {
483
446
  try {
484
447
  const homeDir = os_1.default.homedir();
448
+ let restoredAnyFile = false;
485
449
  // Restore Codex config.toml
486
450
  const codexDir = path_1.default.join(homeDir, '.codex');
487
451
  const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
488
452
  const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.aicodeswitch_backup');
489
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 = {};
490
458
  if (fs_1.default.existsSync(codexConfigPath)) {
491
- 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
+ }
492
465
  }
493
- 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;
494
473
  }
495
474
  // Restore Codex auth.json
496
475
  const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
497
476
  const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
498
477
  if (fs_1.default.existsSync(codexAuthBakPath)) {
478
+ // 读取备份配置
479
+ const backupAuth = JSON.parse(fs_1.default.readFileSync(codexAuthBakPath, 'utf-8'));
480
+ // 读取当前配置(可能包含工具运行时写入的新内容)
481
+ let currentAuth = {};
499
482
  if (fs_1.default.existsSync(codexAuthPath)) {
500
- 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
+ }
501
489
  }
502
- 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;
503
497
  }
504
498
  // 删除元数据
505
499
  (0, config_metadata_1.deleteMetadata)('codex');
506
- return true;
500
+ return restoredAnyFile;
507
501
  }
508
502
  catch (error) {
509
503
  console.error('Failed to restore Codex config files:', error);
@@ -538,6 +532,33 @@ const checkCodexBackupExists = () => {
538
532
  return false;
539
533
  }
540
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
+ });
541
562
  const getCentralSkillsDir = () => {
542
563
  return path_1.default.join(os_1.default.homedir(), '.aicodeswitch', 'skills');
543
564
  };
@@ -928,19 +949,9 @@ const registerRoutes = (dbManager, proxyServer) => {
928
949
  });
929
950
  app.get('/api/vendors', (_req, res) => res.json(dbManager.getVendors()));
930
951
  app.post('/api/vendors', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
931
- const error = validateOpenAIServiceBaseUrlsInVendorPayload(req.body);
932
- if (error) {
933
- res.status(400).json({ error });
934
- return;
935
- }
936
952
  res.json(yield dbManager.createVendor(req.body));
937
953
  })));
938
954
  app.put('/api/vendors/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
939
- const error = validateOpenAIServiceBaseUrlsInVendorPayload(req.body);
940
- if (error) {
941
- res.status(400).json({ error });
942
- return;
943
- }
944
955
  res.json(yield dbManager.updateVendor(req.params.id, req.body));
945
956
  })));
946
957
  app.delete('/api/vendors/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -960,11 +971,6 @@ const registerRoutes = (dbManager, proxyServer) => {
960
971
  });
961
972
  app.post('/api/services', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
962
973
  console.log('[创建服务] 请求数据:', JSON.stringify(req.body, null, 2));
963
- const error = validateOpenAIServiceBaseUrl(req.body);
964
- if (error) {
965
- res.status(400).json({ error });
966
- return;
967
- }
968
974
  const result = yield dbManager.createAPIService(req.body);
969
975
  console.log('[创建服务] 创建结果:', JSON.stringify(result, null, 2));
970
976
  res.json(result);
@@ -975,12 +981,6 @@ const registerRoutes = (dbManager, proxyServer) => {
975
981
  res.status(404).json({ error: '服务不存在' });
976
982
  return;
977
983
  }
978
- const mergedService = Object.assign(Object.assign({}, existingService), req.body);
979
- const error = validateOpenAIServiceBaseUrl(mergedService);
980
- if (error) {
981
- res.status(400).json({ error });
982
- return;
983
- }
984
984
  res.json(yield dbManager.updateAPIService(req.params.id, req.body));
985
985
  })));
986
986
  app.delete('/api/services/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -1027,26 +1027,8 @@ const registerRoutes = (dbManager, proxyServer) => {
1027
1027
  })));
1028
1028
  // 批量停用所有激活的路由(用于应用关闭时清理)
1029
1029
  app.post('/api/routes/deactivate-all', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
1030
- console.log('[Deactivate All Routes] Starting cleanup process...');
1031
- // 步骤1:恢复 Claude Code 配置文件
1032
- try {
1033
- console.log('[Deactivate All Routes] Restoring Claude Code config...');
1034
- const claudeRestored = yield restoreClaudeConfig();
1035
- console.log(`[Deactivate All Routes] Claude Code config ${claudeRestored ? 'restored' : 'was not modified'}`);
1036
- }
1037
- catch (error) {
1038
- console.error('[Deactivate All Routes] Failed to restore Claude config:', error);
1039
- }
1040
- // 步骤2:恢复 Codex 配置文件
1041
- try {
1042
- console.log('[Deactivate All Routes] Restoring Codex config...');
1043
- const codexRestored = yield restoreCodexConfig();
1044
- console.log(`[Deactivate All Routes] Codex config ${codexRestored ? 'restored' : 'was not modified'}`);
1045
- }
1046
- catch (error) {
1047
- console.error('[Deactivate All Routes] Failed to restore Codex config:', error);
1048
- }
1049
- // 步骤3:停用所有激活的路由
1030
+ console.log('[Deactivate All Routes] Starting route deactivation...');
1031
+ // 仅停用路由,不再在路由接口内处理配置文件恢复
1050
1032
  console.log('[Deactivate All Routes] Deactivating all active routes...');
1051
1033
  const deactivatedCount = yield dbManager.deactivateAllRoutes();
1052
1034
  if (deactivatedCount > 0) {
@@ -1057,7 +1039,7 @@ const registerRoutes = (dbManager, proxyServer) => {
1057
1039
  else {
1058
1040
  console.log('[Deactivate All Routes] No active routes to deactivate');
1059
1041
  }
1060
- console.log('[Deactivate All Routes] Cleanup process completed');
1042
+ console.log('[Deactivate All Routes] Route deactivation completed');
1061
1043
  res.json({
1062
1044
  success: true,
1063
1045
  deactivatedCount
@@ -1100,6 +1082,28 @@ const registerRoutes = (dbManager, proxyServer) => {
1100
1082
  res.status(500).json({ error: 'Failed to clear blacklist' });
1101
1083
  }
1102
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
+ })));
1103
1107
  // 获取规则的黑名单状态
1104
1108
  app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1105
1109
  const { routeId } = req.params;
@@ -1189,8 +1193,10 @@ const registerRoutes = (dbManager, proxyServer) => {
1189
1193
  const config = req.body;
1190
1194
  const result = yield dbManager.updateConfig(config);
1191
1195
  if (result) {
1192
- yield proxyServer.updateConfig(config);
1193
- updateProxyConfig(config);
1196
+ const latestConfig = dbManager.getConfig();
1197
+ yield proxyServer.updateConfig(latestConfig);
1198
+ updateProxyConfig(latestConfig);
1199
+ yield syncConfigsOnGlobalConfigUpdate(dbManager);
1194
1200
  }
1195
1201
  res.json(result);
1196
1202
  })));
@@ -1531,29 +1537,53 @@ ${instruction}
1531
1537
  res.json({ success: true });
1532
1538
  })));
1533
1539
  app.post('/api/write-config/claude', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1534
- const enableAgentTeams = req.body.enableAgentTeams;
1535
- const enableBypassPermissionsSupport = req.body.enableBypassPermissionsSupport;
1536
- 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);
1537
1550
  res.json(result);
1538
1551
  })));
1539
1552
  app.post('/api/write-config/codex', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1553
+ const appConfig = dbManager.getConfig();
1540
1554
  const requestedEffort = req.body.modelReasoningEffort;
1541
1555
  const modelReasoningEffort = isCodexReasoningEffort(requestedEffort)
1542
1556
  ? requestedEffort
1543
- : DEFAULT_CODEX_REASONING_EFFORT;
1544
- 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);
1545
1561
  res.json(result);
1546
1562
  })));
1547
- // 更新Claude Code配置中的Agent Teams设置(当路由已激活时)
1563
+ // 兼容接口:更新全局 Agent Teams 配置
1548
1564
  app.post('/api/update-claude-agent-teams', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1549
1565
  const { enableAgentTeams } = req.body;
1550
- 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
+ }
1551
1574
  res.json(result);
1552
1575
  })));
1553
- // 更新Claude Code配置中的bypassPermissions支持设置(当路由已激活时)
1576
+ // 兼容接口:更新全局 bypassPermissions 支持配置
1554
1577
  app.post('/api/update-claude-bypass-permissions-support', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
1555
1578
  const { enableBypassPermissionsSupport } = req.body;
1556
- 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
+ }
1557
1587
  res.json(result);
1558
1588
  })));
1559
1589
  app.post('/api/update-codex-reasoning-effort', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -1562,7 +1592,14 @@ ${instruction}
1562
1592
  res.status(400).json({ error: 'Invalid modelReasoningEffort' });
1563
1593
  return;
1564
1594
  }
1565
- 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
+ }
1566
1603
  res.json(result);
1567
1604
  })));
1568
1605
  app.post('/api/restore-config/claude', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -1872,6 +1909,15 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1872
1909
  console.log('[Server] Initializing database...');
1873
1910
  const dbManager = yield database_factory_1.DatabaseFactory.createAuto(dataDir, legacyDataDir);
1874
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
+ }
1875
1921
  const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
1876
1922
  // Initialize proxy server and register proxy routes last
1877
1923
  proxyServer.initialize();
@@ -1902,6 +1948,12 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1902
1948
  const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
1903
1949
  // 创建 WebSocket 服务器用于规则状态
1904
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
+ }));
1905
1957
  // 将 WebSocket 服务器附加到 HTTP 服务器
1906
1958
  server.on('upgrade', (request, socket, head) => {
1907
1959
  if (request.url === '/api/tools/install') {
@@ -1920,16 +1972,46 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1920
1972
  });
1921
1973
  console.log(`WebSocket server for tool installation attached to ws://${host}:${port}/api/tools/install`);
1922
1974
  console.log(`WebSocket server for rules status attached to ws://${host}:${port}/api/rules/status`);
1923
- const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
1924
- console.log('Shutting down server...');
1925
- dbManager.close();
1926
- 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
+ ]);
1927
2008
  console.log('Server stopped.');
1928
2009
  process.exit(0);
1929
- });
2010
+ }))();
2011
+ return shutdownPromise;
1930
2012
  });
1931
- process.on('SIGINT', shutdown);
1932
- process.on('SIGTERM', shutdown);
2013
+ process.on('SIGINT', () => { void shutdown('SIGINT'); });
2014
+ process.on('SIGTERM', () => { void shutdown('SIGTERM'); });
1933
2015
  });
1934
2016
  // 全局未捕获异常处理 - 防止服务崩溃
1935
2017
  process.on('uncaughtException', (error) => {