yymaxapi 1.0.79 → 1.0.80

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.
Files changed (2) hide show
  1. package/bin/yymaxapi.js +163 -41
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -468,6 +468,53 @@ function buildAuthCandidates(baseDirs) {
468
468
  return auths;
469
469
  }
470
470
 
471
+ function buildPosixAuthCandidates(baseDirs) {
472
+ const auths = [];
473
+ for (const baseDir of baseDirs) {
474
+ auths.push(
475
+ path.posix.join(baseDir, 'agents', 'main', 'agent', 'auth-profiles.json'),
476
+ path.posix.join(baseDir, 'agent', 'auth-profiles.json')
477
+ );
478
+ }
479
+ return auths;
480
+ }
481
+
482
+ function findExistingWslFile(candidates = []) {
483
+ for (const candidate of candidates) {
484
+ const check = safeExec(`wsl -- bash -c "test -f '${candidate}' && echo yes"`, { timeout: 5000 });
485
+ if (check.ok && check.output.trim() === 'yes') {
486
+ return candidate;
487
+ }
488
+ }
489
+ return null;
490
+ }
491
+
492
+ function getWslMirrorInfo() {
493
+ if (process.platform !== 'win32' || !isWslAvailable()) {
494
+ return { configPath: null, authProfiles: null };
495
+ }
496
+
497
+ const wslHome = getWslHome() || '/root';
498
+ const configCandidates = [
499
+ `${wslHome}/.openclaw/openclaw.json`,
500
+ `${wslHome}/.openclaw/moltbot.json`,
501
+ `${wslHome}/.clawdbot/openclaw.json`,
502
+ `${wslHome}/.clawdbot/clawdbot.json`,
503
+ '/root/.openclaw/openclaw.json',
504
+ '/root/.openclaw/moltbot.json',
505
+ '/root/.clawdbot/openclaw.json',
506
+ '/root/.clawdbot/clawdbot.json'
507
+ ];
508
+
509
+ const configPath = findExistingWslFile(configCandidates);
510
+ const authBases = configPath
511
+ ? [path.posix.dirname(configPath)]
512
+ : [`${wslHome}/.openclaw`, `${wslHome}/.clawdbot`, '/root/.openclaw', '/root/.clawdbot'];
513
+ const authProfiles = findExistingWslFile(buildPosixAuthCandidates(authBases));
514
+
515
+ return { configPath, authProfiles };
516
+ }
517
+
471
518
  function getConfigPath() {
472
519
  const homeDir = os.homedir();
473
520
  const openclawStateDir = process.env.OPENCLAW_STATE_DIR || path.join(homeDir, '.openclaw');
@@ -516,29 +563,24 @@ function getConfigPath() {
516
563
  }
517
564
 
518
565
  // Windows + WSL: 尝试读取 WSL 内的配置文件
566
+ let wslConfigPath = null;
567
+ let wslAuthProfiles = null;
519
568
  if (process.platform === 'win32' && isWslAvailable()) {
520
569
  try {
521
- const wslHome = getWslHome() || '/root';
522
- const wslPaths = [
523
- `${wslHome}/.openclaw/openclaw.json`,
524
- `${wslHome}/.openclaw/moltbot.json`,
525
- `${wslHome}/.clawdbot/openclaw.json`,
526
- '/root/.openclaw/openclaw.json',
527
- '/root/.openclaw/moltbot.json',
528
- ];
529
- for (const wp of wslPaths) {
530
- const check = safeExec(`wsl -- bash -c "test -f '${wp}' && echo yes"`, { timeout: 5000 });
531
- if (check.ok && check.output.trim() === 'yes') {
532
- // 将 WSL 配置复制到 Windows 侧,保持同步
533
- const winDest = path.join(openclawStateDir, path.basename(wp));
534
- try {
535
- const content = execFileSync('wsl', ['bash', '-c', `cat '${wp}'`], { encoding: 'utf8', timeout: 10000, stdio: 'pipe' });
536
- if (!fs.existsSync(openclawStateDir)) fs.mkdirSync(openclawStateDir, { recursive: true });
570
+ const mirrorInfo = getWslMirrorInfo();
571
+ wslConfigPath = mirrorInfo.configPath;
572
+ wslAuthProfiles = mirrorInfo.authProfiles;
573
+
574
+ if (wslConfigPath) {
575
+ const winDest = path.join(openclawStateDir, path.basename(wslConfigPath));
576
+ try {
577
+ if (!fs.existsSync(openclawStateDir)) fs.mkdirSync(openclawStateDir, { recursive: true });
578
+ if (!fs.existsSync(winDest)) {
579
+ const content = execFileSync('wsl', ['bash', '-c', `cat '${wslConfigPath}'`], { encoding: 'utf8', timeout: 10000, stdio: 'pipe' });
537
580
  fs.writeFileSync(winDest, content, 'utf8');
538
- if (!candidates.includes(winDest)) candidates.unshift(winDest);
539
- } catch { }
540
- break;
541
- }
581
+ }
582
+ if (!candidates.includes(winDest)) candidates.unshift(winDest);
583
+ } catch { }
542
584
  }
543
585
  } catch { }
544
586
  }
@@ -559,6 +601,9 @@ function getConfigPath() {
559
601
  : [...baseAuthCandidates, ...moltbotAuthCandidates];
560
602
 
561
603
  const authProfiles = authCandidates.find(p => fs.existsSync(p)) || authCandidates[0];
604
+ const authSyncTargets = process.platform === 'win32'
605
+ ? [...new Set(buildAuthCandidates([openclawStateDir, clawdbotStateDir]))].filter(p => p !== authProfiles)
606
+ : [];
562
607
 
563
608
  const syncTargets = [];
564
609
  if (openclawConfig.startsWith(openclawStateDir) && fs.existsSync(clawdbotStateDir)) {
@@ -568,7 +613,7 @@ function getConfigPath() {
568
613
  );
569
614
  }
570
615
 
571
- return { openclawConfig, authProfiles, configDir, syncTargets };
616
+ return { openclawConfig, authProfiles, configDir, syncTargets, authSyncTargets, wslConfigPath, wslAuthProfiles };
572
617
  }
573
618
 
574
619
  // ============ 配置读写 ============
@@ -879,10 +924,15 @@ function syncClawdbotConfigs(paths, config) {
879
924
  function writeConfigWithSync(paths, config) {
880
925
  writeConfig(paths.openclawConfig, config);
881
926
  syncClawdbotConfigs(paths, config);
927
+ const hasWslMirror = process.platform === 'win32' && !!paths.wslConfigPath;
928
+ if (hasWslMirror) {
929
+ syncConfigToWsl(paths.openclawConfig, paths.wslConfigPath);
930
+ }
931
+ invalidateGatewayEnvCache();
882
932
  // 如果 Gateway 在 WSL,自动同步配置过去
883
933
  const gwEnv = detectGatewayEnv();
884
- if (gwEnv === 'wsl') {
885
- syncConfigToWsl(paths.openclawConfig);
934
+ if (!hasWslMirror && gwEnv === 'wsl') {
935
+ syncConfigToWsl(paths.openclawConfig, paths.wslConfigPath);
886
936
  }
887
937
  // 如果 Gateway 在 Docker 容器内,自动同步配置过去
888
938
  if (gwEnv === 'docker' && _selectedDockerContainer) {
@@ -1081,6 +1131,10 @@ let _gwEnvCache = null;
1081
1131
  let _wslAvailCache = null;
1082
1132
  let _wslHomeCache = undefined; // undefined = 未检测, null = 检测失败
1083
1133
 
1134
+ function invalidateGatewayEnvCache() {
1135
+ _gwEnvCache = null;
1136
+ }
1137
+
1084
1138
  function isWslAvailable() {
1085
1139
  if (process.platform !== 'win32') return false;
1086
1140
  if (_wslAvailCache !== null) return _wslAvailCache;
@@ -1206,16 +1260,12 @@ function execAsyncInGatewayEnv(cmd, options = {}) {
1206
1260
  }
1207
1261
 
1208
1262
  // 同步配置到 WSL(仅在 Gateway 环境为 WSL 时调用)
1209
- function syncConfigToWsl(windowsConfigPath) {
1263
+ function syncConfigToWsl(windowsConfigPath, wslDestPath) {
1210
1264
  try {
1211
1265
  const wslHome = getWslHome();
1212
- if (!wslHome) return;
1213
- const winNorm = windowsConfigPath.replace(/\\/g, '/');
1214
- const match = winNorm.match(/^([A-Za-z]):\/(.*)/);
1215
- if (!match) return;
1216
- const wslSrc = `/mnt/${match[1].toLowerCase()}/${match[2]}`;
1217
- const wslDest = `${wslHome}/.openclaw/openclaw.json`;
1218
- execFileSync('wsl', ['bash', '-c', `mkdir -p "${wslHome}/.openclaw" && cp "${wslSrc}" "${wslDest}"`], { timeout: 10000, stdio: 'pipe' });
1266
+ if (!wslHome && !wslDestPath) return;
1267
+ const target = wslDestPath || `${wslHome}/.openclaw/openclaw.json`;
1268
+ syncFileToWsl(windowsConfigPath, target);
1219
1269
  } catch { /* best-effort */ }
1220
1270
  }
1221
1271
 
@@ -1412,9 +1462,70 @@ function readAuthStore(authProfilesPath) {
1412
1462
  }
1413
1463
 
1414
1464
  function writeAuthStore(authProfilesPath, store) {
1465
+ const authDir = path.dirname(authProfilesPath);
1466
+ if (!fs.existsSync(authDir)) {
1467
+ fs.mkdirSync(authDir, { recursive: true });
1468
+ }
1415
1469
  fs.writeFileSync(authProfilesPath, JSON.stringify(store, null, 2), 'utf8');
1416
1470
  }
1417
1471
 
1472
+ function toWslMountPath(windowsPath) {
1473
+ const winNorm = String(windowsPath || '').replace(/\\/g, '/');
1474
+ const match = winNorm.match(/^([A-Za-z]):\/(.*)/);
1475
+ if (!match) return null;
1476
+ return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
1477
+ }
1478
+
1479
+ function syncFileToWsl(windowsPath, wslDestPath) {
1480
+ if (process.platform !== 'win32' || !wslDestPath) return false;
1481
+ const wslSrc = toWslMountPath(windowsPath);
1482
+ if (!wslSrc) return false;
1483
+
1484
+ const wslDir = path.posix.dirname(wslDestPath);
1485
+ execFileSync('wsl', ['bash', '-lc', `mkdir -p ${shellQuote(wslDir)} && cp ${shellQuote(wslSrc)} ${shellQuote(wslDestPath)}`], {
1486
+ timeout: 10000,
1487
+ stdio: 'pipe'
1488
+ });
1489
+ return true;
1490
+ }
1491
+
1492
+ function syncAuthProfilesToWsl(windowsAuthProfilesPath, wslDestPath) {
1493
+ try {
1494
+ const wslHome = getWslHome();
1495
+ if (!wslHome && !wslDestPath) return;
1496
+ const target = wslDestPath || path.posix.join(wslHome, '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
1497
+ syncFileToWsl(windowsAuthProfilesPath, target);
1498
+ } catch { /* best-effort */ }
1499
+ }
1500
+
1501
+ function syncMirroredAuthStores(paths) {
1502
+ if (!paths?.authProfiles) return;
1503
+
1504
+ const store = readAuthStore(paths.authProfiles);
1505
+ for (const target of [...new Set(paths.authSyncTargets || [])]) {
1506
+ if (!target || target === paths.authProfiles) continue;
1507
+ try {
1508
+ writeAuthStore(target, store);
1509
+ } catch { /* best-effort */ }
1510
+ }
1511
+
1512
+ if (process.platform === 'win32' && (paths.wslAuthProfiles || paths.wslConfigPath)) {
1513
+ const derivedWslAuthPath = paths.wslAuthProfiles || path.posix.join(path.posix.dirname(paths.wslConfigPath), 'agents', 'main', 'agent', 'auth-profiles.json');
1514
+ syncAuthProfilesToWsl(paths.authProfiles, derivedWslAuthPath);
1515
+ }
1516
+ }
1517
+
1518
+ function pruneAuthProfilesExceptWithSync(paths, keepProviders = []) {
1519
+ const removed = pruneAuthProfilesExcept(paths.authProfiles, keepProviders);
1520
+ syncMirroredAuthStores(paths);
1521
+ return removed;
1522
+ }
1523
+
1524
+ function updateAuthProfilesWithSync(paths, providerName, apiKey) {
1525
+ updateAuthProfiles(paths.authProfiles, providerName, apiKey);
1526
+ syncMirroredAuthStores(paths);
1527
+ }
1528
+
1418
1529
  function pruneAuthProfilesByPrefix(authProfilesPath, prefixBase, keepProviders = []) {
1419
1530
  const keepSet = new Set(keepProviders);
1420
1531
  const store = readAuthStore(authProfilesPath);
@@ -2298,7 +2409,7 @@ async function quickSetup(paths, args = {}) {
2298
2409
 
2299
2410
  if (toRemove.length > 0) {
2300
2411
  pruneProvidersExcept(config, [providerName]);
2301
- pruneAuthProfilesExcept(paths.authProfiles, [providerName]);
2412
+ pruneAuthProfilesExceptWithSync(paths, [providerName]);
2302
2413
  }
2303
2414
 
2304
2415
  config.models.providers[providerName] = {
@@ -2336,7 +2447,7 @@ async function quickSetup(paths, args = {}) {
2336
2447
  ensureGatewaySettings(config);
2337
2448
  if (apiConfig.api.startsWith('openai')) cleanupConflictingEnvVars(config, normalizedBaseUrl, apiKey);
2338
2449
  writeConfigWithSync(paths, config);
2339
- updateAuthProfiles(paths.authProfiles, providerName, apiKey);
2450
+ updateAuthProfilesWithSync(paths, providerName, apiKey);
2340
2451
  ws.succeed('配置写入完成');
2341
2452
 
2342
2453
  console.log(chalk.green(`\n✅ ${typeLabel} 中转已配置完成!`));
@@ -2416,7 +2527,7 @@ async function presetClaude(paths, args = {}) {
2416
2527
  ? pruneProvidersExcept(config, [providerName])
2417
2528
  : [];
2418
2529
  if (removedProviders.length > 0) {
2419
- pruneAuthProfilesExcept(paths.authProfiles, [providerName]);
2530
+ pruneAuthProfilesExceptWithSync(paths, [providerName]);
2420
2531
  }
2421
2532
 
2422
2533
  const baseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
@@ -2520,7 +2631,7 @@ async function presetClaude(paths, args = {}) {
2520
2631
  createTimestampedBackup(paths.openclawConfig, paths.configDir, 'claude');
2521
2632
  ensureGatewaySettings(config);
2522
2633
  writeConfigWithSync(paths, config);
2523
- updateAuthProfiles(paths.authProfiles, providerName, apiKey);
2634
+ updateAuthProfilesWithSync(paths, providerName, apiKey);
2524
2635
  const extSynced = syncExternalTools('claude', baseUrl, apiKey);
2525
2636
  writeSpinner.succeed('配置写入完成');
2526
2637
 
@@ -2610,7 +2721,7 @@ async function presetCodex(paths, args = {}) {
2610
2721
  ? pruneProvidersExcept(config, [providerName])
2611
2722
  : [];
2612
2723
  if (removedProviders.length > 0) {
2613
- pruneAuthProfilesExcept(paths.authProfiles, [providerName]);
2724
+ pruneAuthProfilesExceptWithSync(paths, [providerName]);
2614
2725
  }
2615
2726
 
2616
2727
  const baseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
@@ -2714,7 +2825,7 @@ async function presetCodex(paths, args = {}) {
2714
2825
  ensureGatewaySettings(config);
2715
2826
  cleanupConflictingEnvVars(config, baseUrl, apiKey);
2716
2827
  writeConfigWithSync(paths, config);
2717
- updateAuthProfiles(paths.authProfiles, providerName, apiKey);
2828
+ updateAuthProfilesWithSync(paths, providerName, apiKey);
2718
2829
  const extSynced2 = syncExternalTools('codex', baseUrl, apiKey);
2719
2830
  writeSpinner2.succeed('配置写入完成');
2720
2831
 
@@ -2898,8 +3009,8 @@ async function autoActivate(paths, args = {}) {
2898
3009
  ensureGatewaySettings(config);
2899
3010
  cleanupConflictingEnvVars(config, codexBaseUrl, apiKey);
2900
3011
  writeConfigWithSync(paths, config);
2901
- updateAuthProfiles(paths.authProfiles, claudeProviderName, apiKey);
2902
- updateAuthProfiles(paths.authProfiles, codexProviderName, apiKey);
3012
+ updateAuthProfilesWithSync(paths, claudeProviderName, apiKey);
3013
+ updateAuthProfilesWithSync(paths, codexProviderName, apiKey);
2903
3014
  const extSynced = [];
2904
3015
  try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl }); extSynced.push('Claude Code settings'); } catch { /* ignore */ }
2905
3016
  try { syncExternalTools('codex', codexBaseUrl, apiKey); extSynced.push('Codex CLI config'); } catch { /* ignore */ }
@@ -3333,8 +3444,8 @@ async function yycodeQuickSetup(paths) {
3333
3444
  ensureGatewaySettings(config);
3334
3445
  cleanupConflictingEnvVars(config, codexBaseUrl, apiKey);
3335
3446
  writeConfigWithSync(paths, config);
3336
- updateAuthProfiles(paths.authProfiles, claudeProviderName, apiKey);
3337
- updateAuthProfiles(paths.authProfiles, codexProviderName, apiKey);
3447
+ updateAuthProfilesWithSync(paths, claudeProviderName, apiKey);
3448
+ updateAuthProfilesWithSync(paths, codexProviderName, apiKey);
3338
3449
  try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl }); } catch { /* ignore */ }
3339
3450
  try { syncExternalTools('codex', codexBaseUrl, apiKey); } catch { /* ignore */ }
3340
3451
  writeSpinner.succeed('配置写入完成');
@@ -3928,6 +4039,7 @@ async function manageToolsProfile(paths) {
3928
4039
  // ============ 测试连接 ============
3929
4040
  async function testConnection(paths, args = {}) {
3930
4041
  console.log(chalk.cyan('🧪 测试 OpenClaw Gateway 连接\n'));
4042
+ invalidateGatewayEnvCache();
3931
4043
 
3932
4044
  const config = readConfig(paths.openclawConfig);
3933
4045
 
@@ -4064,6 +4176,15 @@ async function testConnection(paths, args = {}) {
4064
4176
  console.log(chalk.red(` 期望: ${primary}`));
4065
4177
  console.log(chalk.cyan(` 实际: ${actualModelKey}`));
4066
4178
  console.log(chalk.gray(` 这意味着 ${primary} 无法正常工作,请检查该模型的中转配置`));
4179
+ const configuredFallbacks = config.agents?.defaults?.model?.fallbacks || [];
4180
+ if (configuredFallbacks.length > 0 && !configuredFallbacks.includes(actualModelKey)) {
4181
+ console.log(chalk.yellow(` ⚠️ 实际回退模型不在当前配置的 fallbacks 中,疑似读到了另一份配置`));
4182
+ console.log(chalk.gray(` 当前 fallbacks: ${configuredFallbacks.join(', ')}`));
4183
+ console.log(chalk.gray(` 当前配置文件: ${paths.openclawConfig}`));
4184
+ if (process.platform === 'win32') {
4185
+ console.log(chalk.gray(' Windows 请重点检查: %USERPROFILE%\\.openclaw、%USERPROFILE%\\.clawdbot、WSL ~/.openclaw'));
4186
+ }
4187
+ }
4067
4188
  // 检查是否可能是 api 字段错误导致的 502
4068
4189
  const expectedProvider = primary.split('/')[0];
4069
4190
  const providerCfg = config.models?.providers?.[expectedProvider];
@@ -4185,6 +4306,7 @@ async function testConnection(paths, args = {}) {
4185
4306
  // ============ 重启 Gateway ============
4186
4307
  async function restartGateway({ silent = false } = {}) {
4187
4308
  if (!silent) console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
4309
+ invalidateGatewayEnvCache();
4188
4310
 
4189
4311
  const gwEnv = detectGatewayEnv();
4190
4312
  const configPaths = getConfigPath();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.79",
3
+ "version": "1.0.80",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {