yymaxapi 1.0.12 → 1.0.13

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 +128 -87
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -552,70 +552,126 @@ function syncClawdbotConfigs(paths, config) {
552
552
  function writeConfigWithSync(paths, config) {
553
553
  writeConfig(paths.openclawConfig, config);
554
554
  syncClawdbotConfigs(paths, config);
555
- syncToWsl(paths.openclawConfig);
555
+ // 如果 Gateway 在 WSL,自动同步配置过去
556
+ const gwEnv = detectGatewayEnv();
557
+ if (gwEnv === 'wsl') {
558
+ syncConfigToWsl(paths.openclawConfig);
559
+ }
556
560
  }
557
561
 
558
- // ============ WSL 检测与同步 ============
559
- let _wslDetected = null;
562
+ // ============ Gateway 环境检测(集中化) ============
563
+ // 所有 WSL 相关逻辑统一通过 detectGatewayEnv() 路由
564
+ // 返回 'native' | 'wsl',结果缓存,整个进程生命周期只检测一次
565
+
566
+ let _gwEnvCache = null;
567
+ let _wslAvailCache = null;
568
+ let _wslHomeCache = undefined; // undefined = 未检测, null = 检测失败
560
569
 
561
570
  function isWslAvailable() {
562
571
  if (process.platform !== 'win32') return false;
563
- if (_wslDetected !== null) return _wslDetected;
572
+ if (_wslAvailCache !== null) return _wslAvailCache;
564
573
  try {
565
- const r = execFileSync('wsl', ['--status'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
566
- _wslDetected = true;
574
+ execFileSync('wsl', ['echo', 'ok'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
575
+ _wslAvailCache = true;
567
576
  } catch {
568
- // wsl --status 可能返回非零但 WSL 仍可用,检查 wsl.exe 是否存在
569
- try {
570
- execFileSync('wsl', ['echo', 'ok'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
571
- _wslDetected = true;
572
- } catch {
573
- _wslDetected = false;
574
- }
577
+ _wslAvailCache = false;
575
578
  }
576
- return _wslDetected;
579
+ return _wslAvailCache;
577
580
  }
578
581
 
579
582
  function getWslHome() {
583
+ if (_wslHomeCache !== undefined) return _wslHomeCache;
580
584
  try {
581
- const home = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000 }).trim();
582
- return home || null;
583
- } catch { return null; }
585
+ _wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000 }).trim() || null;
586
+ } catch { _wslHomeCache = null; }
587
+ return _wslHomeCache;
584
588
  }
585
589
 
586
- function isGatewayInWsl(port = 18789) {
587
- if (!isWslAvailable()) return false;
590
+ function detectGatewayEnv(port = 18789) {
591
+ if (_gwEnvCache !== null) return _gwEnvCache;
592
+ if (process.platform !== 'win32' || !isWslAvailable()) {
593
+ _gwEnvCache = 'native';
594
+ return 'native';
595
+ }
596
+ // 检查占用 gateway 端口的进程是否是 wslrelay
588
597
  try {
589
- // 检查占用 gateway 端口的进程是否是 wslrelay
590
598
  const r = safeExec(`netstat -ano | findstr ":${port}"`, { timeout: 5000 });
591
- if (!r.ok || !r.output) return false;
592
- const lines = r.output.split('\n').filter(l => l.includes('LISTENING'));
593
- for (const line of lines) {
594
- const pid = line.trim().split(/\s+/).pop();
595
- if (pid) {
596
- const taskR = safeExec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { timeout: 5000 });
597
- if (taskR.ok && taskR.output && taskR.output.toLowerCase().includes('wslrelay')) {
598
- return true;
599
+ if (r.ok && r.output) {
600
+ const lines = r.output.split('\n').filter(l => l.includes('LISTENING'));
601
+ for (const line of lines) {
602
+ const pid = line.trim().split(/\s+/).pop();
603
+ if (pid) {
604
+ const taskR = safeExec(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { timeout: 5000 });
605
+ if (taskR.ok && taskR.output && taskR.output.toLowerCase().includes('wslrelay')) {
606
+ _gwEnvCache = 'wsl';
607
+ return 'wsl';
608
+ }
599
609
  }
600
610
  }
601
611
  }
602
612
  } catch {}
603
- return false;
613
+ _gwEnvCache = 'native';
614
+ return 'native';
604
615
  }
605
616
 
606
- function syncToWsl(windowsConfigPath) {
607
- if (!isWslAvailable()) return;
617
+ // Gateway 环境中执行命令(WSL 自动包裹 wsl -- bash -c)
618
+ function execInGatewayEnv(cmd, options = {}) {
619
+ if (detectGatewayEnv() === 'wsl') {
620
+ const escaped = cmd.replace(/'/g, "'\\''");
621
+ return safeExec(`wsl -- bash -c '${escaped}'`, options);
622
+ }
623
+ return safeExec(cmd, options);
624
+ }
625
+
626
+ // 在 Gateway 环境中执行命令(异步 exec 版本)
627
+ function execAsyncInGatewayEnv(cmd, options = {}) {
628
+ if (detectGatewayEnv() === 'wsl') {
629
+ const escaped = cmd.replace(/'/g, "'\\''");
630
+ return { cmd: `wsl -- bash -c '${escaped}'`, options };
631
+ }
632
+ return { cmd, options };
633
+ }
634
+
635
+ // 同步配置到 WSL(仅在 Gateway 环境为 WSL 时调用)
636
+ function syncConfigToWsl(windowsConfigPath) {
608
637
  try {
609
638
  const wslHome = getWslHome();
610
639
  if (!wslHome) return;
611
- // 将 Windows 路径转为 WSL /mnt/c/ 路径
612
640
  const winNorm = windowsConfigPath.replace(/\\/g, '/');
613
641
  const match = winNorm.match(/^([A-Za-z]):\/(.*)/);
614
642
  if (!match) return;
615
643
  const wslSrc = `/mnt/${match[1].toLowerCase()}/${match[2]}`;
616
644
  const wslDest = `${wslHome}/.openclaw/openclaw.json`;
617
645
  execFileSync('wsl', ['bash', '-c', `mkdir -p "${wslHome}/.openclaw" && cp "${wslSrc}" "${wslDest}"`], { timeout: 10000, stdio: 'ignore' });
618
- } catch { /* ignore - WSL sync is best-effort */ }
646
+ } catch { /* best-effort */ }
647
+ }
648
+
649
+ // 清理 Gateway 环境中的 agent 进程
650
+ function cleanupAgentProcesses() {
651
+ try {
652
+ const { execSync } = require('child_process');
653
+ if (process.platform === 'win32') {
654
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
655
+ try {
656
+ execSync(`wmic process where "commandline like '%${name}%agent%' and not commandline like '%gateway%'" call terminate 2>nul`, { stdio: 'ignore', timeout: 10000 });
657
+ } catch { /* ignore */ }
658
+ }
659
+ try {
660
+ execSync('taskkill /F /FI "IMAGENAME eq node.exe" /FI "STATUS eq Not Responding" 2>nul', { stdio: 'ignore' });
661
+ } catch { /* ignore */ }
662
+ // WSL 内的 agent 也要清理
663
+ if (detectGatewayEnv() === 'wsl') {
664
+ try {
665
+ execSync('wsl -- bash -c "pkill -f \'openclaw.*agent\' 2>/dev/null; pkill -f \'clawdbot.*agent\' 2>/dev/null; pkill -f \'moltbot.*agent\' 2>/dev/null; true"', { stdio: 'ignore', timeout: 10000 });
666
+ } catch { /* ignore */ }
667
+ }
668
+ } else {
669
+ for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
670
+ execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
671
+ }
672
+ }
673
+ console.log(chalk.gray('已清理残留 agent 进程'));
674
+ } catch { /* ignore */ }
619
675
  }
620
676
 
621
677
  function coerceModelsRecord(value) {
@@ -2077,7 +2133,9 @@ function getConfigStatusLine(paths) {
2077
2133
  return chalk.gray('当前状态: 未配置任何模型');
2078
2134
  }
2079
2135
 
2080
- return chalk.gray('当前状态: ') + parts.join(' ') + chalk.gray(' (✓ 主模型 ○ 已配置)');
2136
+ const gwEnv = detectGatewayEnv();
2137
+ const envTag = gwEnv === 'wsl' ? chalk.cyan(' [WSL]') : '';
2138
+ return chalk.gray('当前状态: ') + parts.join(' ') + chalk.gray(' (✓ 主模型 ○ 已配置)') + envTag;
2081
2139
  } catch {
2082
2140
  return null;
2083
2141
  }
@@ -2269,35 +2327,8 @@ async function testConnection(paths, args = {}) {
2269
2327
 
2270
2328
  const allowAutoDaemon = !(args['no-daemon'] || args.noDaemon);
2271
2329
 
2272
- // 步骤0: 清理所有挂死的 agent 进程(不限于 yymaxapi-test)
2273
- try {
2274
- const { execSync } = require('child_process');
2275
- const platform = process.platform;
2276
- if (platform === 'win32') {
2277
- // Windows: 用 wmic 精确匹配 agent 子进程(排除 gateway)
2278
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
2279
- try {
2280
- execSync(`wmic process where "commandline like '%${name}%agent%' and not commandline like '%gateway%'" call terminate 2>nul`, { stdio: 'ignore', timeout: 10000 });
2281
- } catch { /* ignore */ }
2282
- }
2283
- // fallback: 杀掉所有 Not Responding 的 node 进程
2284
- try {
2285
- execSync('taskkill /F /FI "IMAGENAME eq node.exe" /FI "STATUS eq Not Responding" 2>nul', { stdio: 'ignore' });
2286
- } catch { /* ignore */ }
2287
- // WSL 内的 agent 进程也要清理
2288
- if (isWslAvailable()) {
2289
- try {
2290
- execSync('wsl -- bash -c "pkill -f \'openclaw.*agent\' 2>/dev/null; pkill -f \'clawdbot.*agent\' 2>/dev/null; pkill -f \'moltbot.*agent\' 2>/dev/null; true"', { stdio: 'ignore', timeout: 10000 });
2291
- } catch { /* ignore */ }
2292
- }
2293
- } else {
2294
- // Unix: 杀掉所有 openclaw/clawdbot/moltbot agent 子进程(排除 gateway)
2295
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
2296
- execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
2297
- }
2298
- }
2299
- console.log(chalk.gray('已清理残留 agent 进程'));
2300
- } catch { /* ignore */ }
2330
+ // 步骤0: 清理所有挂死的 agent 进程
2331
+ cleanupAgentProcesses();
2301
2332
 
2302
2333
  // 步骤1: 先重启 Gateway 使配置生效
2303
2334
  console.log(chalk.cyan('步骤 1/2: 重启 Gateway 使配置生效...'));
@@ -2443,11 +2474,10 @@ async function testConnection(paths, args = {}) {
2443
2474
  async function restartGateway() {
2444
2475
  console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
2445
2476
 
2446
- const gatewayPort = 18789;
2447
- const gwInWsl = isGatewayInWsl(gatewayPort);
2477
+ const gwEnv = detectGatewayEnv();
2448
2478
 
2449
2479
  // 如果 Gateway 在 WSL 里,优先用 wsl -- 重启
2450
- if (gwInWsl) {
2480
+ if (gwEnv === 'wsl') {
2451
2481
  console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
2452
2482
  return new Promise((resolve) => {
2453
2483
  const wslCmds = [
@@ -2458,7 +2488,7 @@ async function restartGateway() {
2458
2488
  let tried = 0;
2459
2489
  const tryNext = () => {
2460
2490
  if (tried >= wslCmds.length) {
2461
- console.log(chalk.yellow('WSL 内 Gateway 重启失败,尝试 Windows 原生重启...'));
2491
+ console.log(chalk.yellow('WSL 内 Gateway 重启失败,尝试 Windows 原生重启...'));
2462
2492
  restartGatewayNative().then(resolve);
2463
2493
  return;
2464
2494
  }
@@ -2466,7 +2496,7 @@ async function restartGateway() {
2466
2496
  exec(cmd, { timeout: 30000 }, (error) => {
2467
2497
  if (error) { tryNext(); }
2468
2498
  else {
2469
- console.log(chalk.green('Gateway 已重启 (WSL)'));
2499
+ console.log(chalk.green('Gateway 已重启 (WSL)'));
2470
2500
  resolve();
2471
2501
  }
2472
2502
  });
@@ -2622,27 +2652,38 @@ function testGatewayApi(port, token, model, endpoint = '/v1/responses') {
2622
2652
 
2623
2653
  function testGatewayViaAgent(model) {
2624
2654
  return new Promise((resolve) => {
2625
- const { cliBinary, nodeMajor } = getCliMeta();
2626
- if (!cliBinary) {
2627
- resolve({ success: false, usedCli: false, error: '未找到 openclaw/clawdbot/moltbot 命令' });
2628
- return;
2629
- }
2630
-
2631
- const nodeInfo = findCompatibleNode(nodeMajor);
2632
- // 清除可能覆盖配置文件 apiKey 的环境变量,避免 Gateway 使用错误的 key
2633
- const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null), NODE_NO_WARNINGS: '1' };
2634
- delete env.CLAUDE_API_KEY;
2635
- delete env.OPENCLAW_CLAUDE_KEY;
2636
- delete env.OPENCLAW_API_KEY;
2637
- delete env.OPENAI_API_KEY;
2638
- delete env.OPENCLAW_CODEX_KEY;
2639
- const useNode = nodeInfo && isNodeShebang(cliBinary);
2655
+ const gwEnv = detectGatewayEnv();
2640
2656
  const sessionId = `yymaxapi-test-${Date.now()}`;
2641
- const cmd = useNode
2642
- ? `"${nodeInfo.path}" "${cliBinary}" agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`
2643
- : `"${cliBinary}" agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`;
2644
2657
 
2645
- exec(cmd, { timeout: 120000, env }, (error, stdout, stderr) => {
2658
+ let cmd;
2659
+ let execOpts;
2660
+
2661
+ if (gwEnv === 'wsl') {
2662
+ // Gateway 在 WSL 中,agent 也要在 WSL 中执行
2663
+ const agentCmd = `openclaw agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120 2>/dev/null || clawdbot agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120 2>/dev/null || moltbot agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`;
2664
+ cmd = `wsl -- bash -c '${agentCmd.replace(/'/g, "'\\''")}'`;
2665
+ execOpts = { timeout: 120000 };
2666
+ } else {
2667
+ const { cliBinary, nodeMajor } = getCliMeta();
2668
+ if (!cliBinary) {
2669
+ resolve({ success: false, usedCli: false, error: '未找到 openclaw/clawdbot/moltbot 命令' });
2670
+ return;
2671
+ }
2672
+ const nodeInfo = findCompatibleNode(nodeMajor);
2673
+ const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null), NODE_NO_WARNINGS: '1' };
2674
+ delete env.CLAUDE_API_KEY;
2675
+ delete env.OPENCLAW_CLAUDE_KEY;
2676
+ delete env.OPENCLAW_API_KEY;
2677
+ delete env.OPENAI_API_KEY;
2678
+ delete env.OPENCLAW_CODEX_KEY;
2679
+ const useNode = nodeInfo && isNodeShebang(cliBinary);
2680
+ cmd = useNode
2681
+ ? `"${nodeInfo.path}" "${cliBinary}" agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`
2682
+ : `"${cliBinary}" agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`;
2683
+ execOpts = { timeout: 120000, env };
2684
+ }
2685
+
2686
+ exec(cmd, execOpts, (error, stdout, stderr) => {
2646
2687
  // 过滤 stderr 中的 Node.js DeprecationWarning 噪音
2647
2688
  const cleanStderr = (stderr || '').replace(/\(node:\d+\) \[DEP\d+\] DeprecationWarning:.*(\n.*trace-deprecation.*)?/g, '').trim();
2648
2689
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {