yymaxapi 1.0.11 → 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 +191 -44
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -552,6 +552,126 @@ function syncClawdbotConfigs(paths, config) {
552
552
  function writeConfigWithSync(paths, config) {
553
553
  writeConfig(paths.openclawConfig, config);
554
554
  syncClawdbotConfigs(paths, config);
555
+ // 如果 Gateway 在 WSL,自动同步配置过去
556
+ const gwEnv = detectGatewayEnv();
557
+ if (gwEnv === 'wsl') {
558
+ syncConfigToWsl(paths.openclawConfig);
559
+ }
560
+ }
561
+
562
+ // ============ Gateway 环境检测(集中化) ============
563
+ // 所有 WSL 相关逻辑统一通过 detectGatewayEnv() 路由
564
+ // 返回 'native' | 'wsl',结果缓存,整个进程生命周期只检测一次
565
+
566
+ let _gwEnvCache = null;
567
+ let _wslAvailCache = null;
568
+ let _wslHomeCache = undefined; // undefined = 未检测, null = 检测失败
569
+
570
+ function isWslAvailable() {
571
+ if (process.platform !== 'win32') return false;
572
+ if (_wslAvailCache !== null) return _wslAvailCache;
573
+ try {
574
+ execFileSync('wsl', ['echo', 'ok'], { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
575
+ _wslAvailCache = true;
576
+ } catch {
577
+ _wslAvailCache = false;
578
+ }
579
+ return _wslAvailCache;
580
+ }
581
+
582
+ function getWslHome() {
583
+ if (_wslHomeCache !== undefined) return _wslHomeCache;
584
+ try {
585
+ _wslHomeCache = execFileSync('wsl', ['bash', '-c', 'echo $HOME'], { encoding: 'utf8', timeout: 5000 }).trim() || null;
586
+ } catch { _wslHomeCache = null; }
587
+ return _wslHomeCache;
588
+ }
589
+
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
597
+ try {
598
+ const r = safeExec(`netstat -ano | findstr ":${port}"`, { timeout: 5000 });
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
+ }
609
+ }
610
+ }
611
+ }
612
+ } catch {}
613
+ _gwEnvCache = 'native';
614
+ return 'native';
615
+ }
616
+
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) {
637
+ try {
638
+ const wslHome = getWslHome();
639
+ if (!wslHome) return;
640
+ const winNorm = windowsConfigPath.replace(/\\/g, '/');
641
+ const match = winNorm.match(/^([A-Za-z]):\/(.*)/);
642
+ if (!match) return;
643
+ const wslSrc = `/mnt/${match[1].toLowerCase()}/${match[2]}`;
644
+ const wslDest = `${wslHome}/.openclaw/openclaw.json`;
645
+ execFileSync('wsl', ['bash', '-c', `mkdir -p "${wslHome}/.openclaw" && cp "${wslSrc}" "${wslDest}"`], { timeout: 10000, stdio: 'ignore' });
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 */ }
555
675
  }
556
676
 
557
677
  function coerceModelsRecord(value) {
@@ -2013,7 +2133,9 @@ function getConfigStatusLine(paths) {
2013
2133
  return chalk.gray('当前状态: 未配置任何模型');
2014
2134
  }
2015
2135
 
2016
- 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;
2017
2139
  } catch {
2018
2140
  return null;
2019
2141
  }
@@ -2205,29 +2327,8 @@ async function testConnection(paths, args = {}) {
2205
2327
 
2206
2328
  const allowAutoDaemon = !(args['no-daemon'] || args.noDaemon);
2207
2329
 
2208
- // 步骤0: 清理所有挂死的 agent 进程(不限于 yymaxapi-test)
2209
- try {
2210
- const { execSync } = require('child_process');
2211
- const platform = process.platform;
2212
- if (platform === 'win32') {
2213
- // Windows: 用 wmic 精确匹配 agent 子进程(排除 gateway)
2214
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
2215
- try {
2216
- execSync(`wmic process where "commandline like '%${name}%agent%' and not commandline like '%gateway%'" call terminate 2>nul`, { stdio: 'ignore', timeout: 10000 });
2217
- } catch { /* ignore */ }
2218
- }
2219
- // fallback: 杀掉所有 Not Responding 的 node 进程
2220
- try {
2221
- execSync('taskkill /F /FI "IMAGENAME eq node.exe" /FI "STATUS eq Not Responding" 2>nul', { stdio: 'ignore' });
2222
- } catch { /* ignore */ }
2223
- } else {
2224
- // Unix: 杀掉所有 openclaw/clawdbot/moltbot agent 子进程(排除 gateway)
2225
- for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
2226
- execSync(`pkill -f '${name}.*agent' 2>/dev/null || true`, { stdio: 'ignore' });
2227
- }
2228
- }
2229
- console.log(chalk.gray('已清理残留 agent 进程'));
2230
- } catch { /* ignore */ }
2330
+ // 步骤0: 清理所有挂死的 agent 进程
2331
+ cleanupAgentProcesses();
2231
2332
 
2232
2333
  // 步骤1: 先重启 Gateway 使配置生效
2233
2334
  console.log(chalk.cyan('步骤 1/2: 重启 Gateway 使配置生效...'));
@@ -2371,8 +2472,43 @@ async function testConnection(paths, args = {}) {
2371
2472
 
2372
2473
  // ============ 重启 Gateway ============
2373
2474
  async function restartGateway() {
2374
- console.log(chalk.cyan('\n🔄 正在重启 OpenClaw Gateway...'));
2475
+ console.log(chalk.cyan('\n正在重启 OpenClaw Gateway...'));
2476
+
2477
+ const gwEnv = detectGatewayEnv();
2478
+
2479
+ // 如果 Gateway 在 WSL 里,优先用 wsl -- 重启
2480
+ if (gwEnv === 'wsl') {
2481
+ console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
2482
+ return new Promise((resolve) => {
2483
+ const wslCmds = [
2484
+ 'wsl -- bash -c "openclaw gateway restart"',
2485
+ 'wsl -- bash -c "clawdbot gateway restart"',
2486
+ 'wsl -- bash -c "moltbot gateway restart"',
2487
+ ];
2488
+ let tried = 0;
2489
+ const tryNext = () => {
2490
+ if (tried >= wslCmds.length) {
2491
+ console.log(chalk.yellow('WSL 内 Gateway 重启失败,尝试 Windows 原生重启...'));
2492
+ restartGatewayNative().then(resolve);
2493
+ return;
2494
+ }
2495
+ const cmd = wslCmds[tried++];
2496
+ exec(cmd, { timeout: 30000 }, (error) => {
2497
+ if (error) { tryNext(); }
2498
+ else {
2499
+ console.log(chalk.green('Gateway 已重启 (WSL)'));
2500
+ resolve();
2501
+ }
2502
+ });
2503
+ };
2504
+ tryNext();
2505
+ });
2506
+ }
2375
2507
 
2508
+ return restartGatewayNative();
2509
+ }
2510
+
2511
+ async function restartGatewayNative() {
2376
2512
  const { cliBinary: resolved, nodeMajor } = getCliMeta();
2377
2513
  const nodeInfo = findCompatibleNode(nodeMajor);
2378
2514
  const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null) };
@@ -2516,27 +2652,38 @@ function testGatewayApi(port, token, model, endpoint = '/v1/responses') {
2516
2652
 
2517
2653
  function testGatewayViaAgent(model) {
2518
2654
  return new Promise((resolve) => {
2519
- const { cliBinary, nodeMajor } = getCliMeta();
2520
- if (!cliBinary) {
2521
- resolve({ success: false, usedCli: false, error: '未找到 openclaw/clawdbot/moltbot 命令' });
2522
- return;
2523
- }
2524
-
2525
- const nodeInfo = findCompatibleNode(nodeMajor);
2526
- // 清除可能覆盖配置文件 apiKey 的环境变量,避免 Gateway 使用错误的 key
2527
- const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null), NODE_NO_WARNINGS: '1' };
2528
- delete env.CLAUDE_API_KEY;
2529
- delete env.OPENCLAW_CLAUDE_KEY;
2530
- delete env.OPENCLAW_API_KEY;
2531
- delete env.OPENAI_API_KEY;
2532
- delete env.OPENCLAW_CODEX_KEY;
2533
- const useNode = nodeInfo && isNodeShebang(cliBinary);
2655
+ const gwEnv = detectGatewayEnv();
2534
2656
  const sessionId = `yymaxapi-test-${Date.now()}`;
2535
- const cmd = useNode
2536
- ? `"${nodeInfo.path}" "${cliBinary}" agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`
2537
- : `"${cliBinary}" agent --session-id ${sessionId} --message "请回复你的模型名称" --json --timeout 120`;
2538
2657
 
2539
- 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) => {
2540
2687
  // 过滤 stderr 中的 Node.js DeprecationWarning 噪音
2541
2688
  const cleanStderr = (stderr || '').replace(/\(node:\d+\) \[DEP\d+\] DeprecationWarning:.*(\n.*trace-deprecation.*)?/g, '').trim();
2542
2689
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {