yymaxapi 1.0.95 → 1.0.97

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 +161 -6
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -639,6 +639,51 @@ function getGatewayListenOwner(port = 18789) {
639
639
  return ownerResult.output.trim().split(/\s+/)[0] || null;
640
640
  }
641
641
 
642
+ function getGatewayProcessSignature(port = 18789, environment = detectGatewayEnv()) {
643
+ if (!port) return '';
644
+
645
+ let result = null;
646
+
647
+ if (environment === 'docker') {
648
+ if (!_selectedDockerContainer?.id) return '';
649
+ result = safeExec(
650
+ dockerCmd(`exec ${_selectedDockerContainer.id} sh -c "lsof -ti :${port} 2>/dev/null"`),
651
+ { timeout: 3000 }
652
+ );
653
+ } else if (environment === 'wsl') {
654
+ result = safeExec(`wsl -- bash -c "lsof -ti :${port} 2>/dev/null"`, { timeout: 3000 });
655
+ } else if (process.platform === 'win32') {
656
+ result = safeExec(`netstat -ano | findstr ":${port}"`, { timeout: 3000 });
657
+ } else {
658
+ result = safeExec(`lsof -ti :${port} 2>/dev/null`, { timeout: 3000 });
659
+ }
660
+
661
+ const text = [result?.output, result?.stdout, result?.stderr]
662
+ .filter(Boolean)
663
+ .join('\n');
664
+ if (!text) return '';
665
+
666
+ const pids = process.platform === 'win32'
667
+ ? text
668
+ .split('\n')
669
+ .map(line => line.trim())
670
+ .filter(line => line.includes('LISTENING') && line.includes(`:${port}`))
671
+ .map(line => line.split(/\s+/).pop() || '')
672
+ .filter(pid => /^\d+$/.test(pid))
673
+ : text
674
+ .split('\n')
675
+ .map(line => line.trim())
676
+ .filter(pid => /^\d+$/.test(pid));
677
+
678
+ return [...new Set(pids)].sort().join(',');
679
+ }
680
+
681
+ function didGatewayProcessRestart({ portWasOpenBefore, beforeSignature, afterSignature }) {
682
+ if (!portWasOpenBefore) return true;
683
+ if (!beforeSignature || !afterSignature) return true;
684
+ return beforeSignature !== afterSignature;
685
+ }
686
+
642
687
  function getConfigPath() {
643
688
  const homeDir = os.homedir();
644
689
  const openclawStateDir = process.env.OPENCLAW_STATE_DIR || path.join(homeDir, '.openclaw');
@@ -2789,6 +2834,88 @@ function syncMirroredAuthStores(paths) {
2789
2834
  }
2790
2835
  }
2791
2836
 
2837
+ function getManagedAgentSessionDirs(paths, agentId) {
2838
+ if (!paths?.configDir || !agentId) return [];
2839
+ const dirs = [path.join(paths.configDir, 'agents', agentId, 'sessions')];
2840
+
2841
+ for (const target of paths.syncTargets || []) {
2842
+ const baseDir = path.dirname(target);
2843
+ dirs.push(path.join(baseDir, 'agents', agentId, 'sessions'));
2844
+ }
2845
+
2846
+ return [...new Set(dirs.filter(Boolean))];
2847
+ }
2848
+
2849
+ function archiveManagedSessionFile(sessionDir, sessionId) {
2850
+ if (!sessionDir || !sessionId) return false;
2851
+ const sessionFile = path.join(sessionDir, `${sessionId}.jsonl`);
2852
+ if (!fs.existsSync(sessionFile)) return false;
2853
+
2854
+ const archiveDir = path.join(sessionDir, 'archive');
2855
+ if (!fs.existsSync(archiveDir)) {
2856
+ fs.mkdirSync(archiveDir, { recursive: true });
2857
+ }
2858
+
2859
+ const archiveName = `${sessionId}-${Date.now()}.jsonl`;
2860
+ fs.renameSync(sessionFile, path.join(archiveDir, archiveName));
2861
+ return true;
2862
+ }
2863
+
2864
+ function resetManagedAgentSessionStore(sessionDir, agentId) {
2865
+ if (!sessionDir || !agentId) return { changed: false, archived: 0 };
2866
+ const sessionsPath = path.join(sessionDir, 'sessions.json');
2867
+ if (!fs.existsSync(sessionsPath)) return { changed: false, archived: 0 };
2868
+
2869
+ let store;
2870
+ try {
2871
+ store = JSON.parse(fs.readFileSync(sessionsPath, 'utf8'));
2872
+ } catch {
2873
+ return { changed: false, archived: 0 };
2874
+ }
2875
+
2876
+ if (!store || typeof store !== 'object' || Array.isArray(store)) {
2877
+ return { changed: false, archived: 0 };
2878
+ }
2879
+
2880
+ const keyPrefix = `agent:${agentId}:`;
2881
+ const keysToRemove = Object.keys(store).filter(key => key.startsWith(keyPrefix));
2882
+ if (keysToRemove.length === 0) return { changed: false, archived: 0 };
2883
+
2884
+ const remaining = { ...store };
2885
+ const removedSessionIds = new Set();
2886
+ for (const key of keysToRemove) {
2887
+ const sessionId = String(store[key]?.sessionId || '').trim();
2888
+ if (sessionId) removedSessionIds.add(sessionId);
2889
+ delete remaining[key];
2890
+ }
2891
+
2892
+ let archived = 0;
2893
+ for (const sessionId of removedSessionIds) {
2894
+ const stillReferenced = Object.values(remaining).some(entry => String(entry?.sessionId || '').trim() === sessionId);
2895
+ if (stillReferenced) continue;
2896
+ if (archiveManagedSessionFile(sessionDir, sessionId)) archived += 1;
2897
+ }
2898
+
2899
+ fs.writeFileSync(sessionsPath, JSON.stringify(remaining, null, 2), 'utf8');
2900
+ return { changed: true, archived };
2901
+ }
2902
+
2903
+ function resetManagedAgentSessionsWithSync(paths, agentId) {
2904
+ const dirs = getManagedAgentSessionDirs(paths, agentId);
2905
+ let changed = false;
2906
+ let archived = 0;
2907
+
2908
+ for (const sessionDir of dirs) {
2909
+ try {
2910
+ const result = resetManagedAgentSessionStore(sessionDir, agentId);
2911
+ if (result.changed) changed = true;
2912
+ archived += result.archived || 0;
2913
+ } catch { /* best-effort */ }
2914
+ }
2915
+
2916
+ return { changed, archived };
2917
+ }
2918
+
2792
2919
  function renameProviderInAuthStore(paths, oldProvider, newProvider) {
2793
2920
  if (!paths?.authProfiles || !oldProvider || !newProvider || oldProvider === newProvider) return false;
2794
2921
  const store = readAuthStore(paths.authProfiles);
@@ -4571,6 +4698,12 @@ async function autoActivate(paths, args = {}) {
4571
4698
  if (yunyiLayoutResult.applied) {
4572
4699
  syncManagedYunyiAuthProfiles(paths, config);
4573
4700
  }
4701
+ if (!isClaudePrimary) {
4702
+ const resetResult = resetManagedAgentSessionsWithSync(paths, YYMAXAPI_OPENCLAW_GPT_AGENT_ID);
4703
+ if (resetResult.changed) {
4704
+ console.log(chalk.gray(` 已重置 ${YYMAXAPI_OPENCLAW_GPT_AGENT_ID} 的活动会话映射`));
4705
+ }
4706
+ }
4574
4707
  const opencodeDefaultModelKey = isClaudePrimary ? `yunyi-claude/${claudeModelId}` : `yunyi-codex/${codexModelId}`;
4575
4708
  try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl, claudeModelId, opencodeDefaultModelKey }); } catch { /* ignore */ }
4576
4709
  try { syncExternalTools('codex', codexBaseUrl, apiKey, { modelId: codexModelId }); } catch { /* ignore */ }
@@ -5544,6 +5677,12 @@ async function switchModel(paths) {
5544
5677
  primary = finalSelected;
5545
5678
  ensureGatewaySettings(config);
5546
5679
  writeConfigWithSync(paths, config);
5680
+ if (selectedAgentId === YYMAXAPI_OPENCLAW_GPT_AGENT_ID) {
5681
+ const resetResult = resetManagedAgentSessionsWithSync(paths, selectedAgentId);
5682
+ if (resetResult.changed) {
5683
+ console.log(chalk.gray(` 已重置 ${selectedAgentId} 的活动会话映射`));
5684
+ }
5685
+ }
5547
5686
 
5548
5687
  const selectedProviderKey = finalSelected.split('/')[0];
5549
5688
  const selectedProviderConfig = providers[selectedProviderKey];
@@ -5940,14 +6079,19 @@ async function restartGatewayDocker(gatewayPort, silent = false) {
5940
6079
 
5941
6080
  const cid = container.id;
5942
6081
  const shellVariants = ['sh -c', 'bash -lc', 'bash -c'];
6082
+ const portWasOpenBefore = await isPortOpen(gatewayPort, '127.0.0.1', 500);
6083
+ const beforeSignature = portWasOpenBefore ? getGatewayProcessSignature(gatewayPort, 'docker') : '';
5943
6084
 
5944
6085
  // 策略 A:exec restart 命令(短超时),然后端口探测
5945
6086
  for (const cmd of buildDockerInnerCmds(container, 'gateway restart')) {
5946
6087
  for (const shell of shellVariants) {
5947
6088
  safeExec(dockerCmd(`exec ${cid} ${shell} "${cmd}"`), { timeout: 8000 });
5948
6089
  if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
5949
- if (!silent) console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
5950
- return true;
6090
+ const afterSignature = getGatewayProcessSignature(gatewayPort, 'docker');
6091
+ if (didGatewayProcessRestart({ portWasOpenBefore, beforeSignature, afterSignature })) {
6092
+ if (!silent) console.log(chalk.green(`✅ Gateway 已重启 (Docker: ${container.name})`));
6093
+ return true;
6094
+ }
5951
6095
  }
5952
6096
  }
5953
6097
  }
@@ -5980,6 +6124,8 @@ async function restartGatewayWsl(gatewayPort, silent = false) {
5980
6124
  if (!silent) console.log(chalk.gray(' [检测] Gateway 运行在 WSL 中'));
5981
6125
  const wslCli = getWslCliBinary();
5982
6126
  const names = ['openclaw', 'clawdbot', 'moltbot'];
6127
+ const portWasOpenBefore = await isPortOpen(gatewayPort, '127.0.0.1', 500);
6128
+ const beforeSignature = portWasOpenBefore ? getGatewayProcessSignature(gatewayPort, 'wsl') : '';
5983
6129
 
5984
6130
  // 构建 WSL 重启命令
5985
6131
  const wslRestartCmds = [];
@@ -5990,8 +6136,11 @@ async function restartGatewayWsl(gatewayPort, silent = false) {
5990
6136
  for (const cmd of wslRestartCmds) {
5991
6137
  safeExec(cmd, { timeout: 8000 });
5992
6138
  if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
5993
- if (!silent) console.log(chalk.green('✅ Gateway 已重启 (WSL)'));
5994
- return true;
6139
+ const afterSignature = getGatewayProcessSignature(gatewayPort, 'wsl');
6140
+ if (didGatewayProcessRestart({ portWasOpenBefore, beforeSignature, afterSignature })) {
6141
+ if (!silent) console.log(chalk.green('✅ Gateway 已重启 (WSL)'));
6142
+ return true;
6143
+ }
5995
6144
  }
5996
6145
  }
5997
6146
 
@@ -6030,6 +6179,8 @@ async function restartGatewayNative(silent = false) {
6030
6179
  const configPaths = getConfigPath();
6031
6180
  const gwConfig = readConfig(configPaths.openclawConfig);
6032
6181
  const gatewayPort = gwConfig?.gateway?.port || 18789;
6182
+ const portWasOpenBefore = await isPortOpen(gatewayPort, '127.0.0.1', 500);
6183
+ const beforeSignature = portWasOpenBefore ? getGatewayProcessSignature(gatewayPort, 'native') : '';
6033
6184
 
6034
6185
  // 策略 A:尝试 exec "gateway restart"(短超时),然后端口探测
6035
6186
  const restartCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'restart');
@@ -6037,8 +6188,12 @@ async function restartGatewayNative(silent = false) {
6037
6188
  // 只尝试第一条(最精确的路径),避免逐条超时累积
6038
6189
  safeExec(restartCmds[0], { timeout: 8000, env });
6039
6190
  if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
6040
- if (!silent) console.log(chalk.green(`✅ Gateway 已重启`));
6041
- return true;
6191
+ const afterSignature = getGatewayProcessSignature(gatewayPort, 'native');
6192
+ if (didGatewayProcessRestart({ portWasOpenBefore, beforeSignature, afterSignature })) {
6193
+ if (!silent) console.log(chalk.green(`✅ Gateway 已重启`));
6194
+ return true;
6195
+ }
6196
+ if (!silent) console.log(chalk.gray(' 检测到 Gateway 监听进程未变化,继续执行强制重启...'));
6042
6197
  }
6043
6198
  }
6044
6199
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.95",
3
+ "version": "1.0.97",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {