fix: preserve telegram proxy on gateway restart after doctor (#546)
Co-authored-by: zuolingxuan <zuolingxuan@bytedance.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
e10ff3a1fb
commit
56701d823c
@@ -190,6 +190,8 @@ ClawXには、Electron、OpenClaw Gateway、またはTelegramなどのチャネ
|
||||
- 高度なプロキシフィールドが空の場合、ClawXは`プロキシサーバー`にフォールバックします。
|
||||
- プロキシ設定を保存すると、Electronのネットワーク設定が即座に再適用され、ゲートウェイが自動的に再起動されます。
|
||||
- ClawXはTelegramが有効な場合、プロキシをOpenClawのTelegramチャネル設定にも同期します。
|
||||
- ClawXのプロキシが無効な状態では、Gatewayの通常再起動時に既存のTelegramチャネルプロキシ設定を保持します。
|
||||
- OpenClaw設定のTelegramプロキシを明示的に消したい場合は、プロキシ無効の状態で一度「保存」を実行してください。
|
||||
- **設定 → 詳細 → 開発者** では **OpenClaw Doctor** を実行でき、`openclaw doctor --json` の診断出力をアプリ内で確認できます。
|
||||
- Windows のパッケージ版では、同梱された `openclaw` CLI/TUI は端末入力を安定させるため、同梱の `node.exe` エントリーポイント経由で実行されます。
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ Notes:
|
||||
- If advanced proxy fields are left empty, ClawX falls back to `Proxy Server`.
|
||||
- Saving proxy settings reapplies Electron networking immediately and restarts the Gateway automatically.
|
||||
- ClawX also syncs the proxy to OpenClaw's Telegram channel config when Telegram is enabled.
|
||||
- Gateway restarts preserve an existing Telegram channel proxy if ClawX proxy is currently disabled.
|
||||
- To explicitly clear Telegram channel proxy from OpenClaw config, save proxy settings with proxy disabled.
|
||||
- In **Settings → Advanced → Developer**, you can run **OpenClaw Doctor** to execute `openclaw doctor --json` and inspect the diagnostic output without leaving the app.
|
||||
- On packaged Windows builds, the bundled `openclaw` CLI/TUI runs via the shipped `node.exe` entrypoint to keep terminal input behavior stable.
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ ClawX 内置了代理设置,适用于需要通过本地代理客户端访问
|
||||
- 高级代理项留空时,会自动回退到“代理服务器”。
|
||||
- 保存代理设置后,Electron 网络层会立即重新应用代理,并自动重启 Gateway。
|
||||
- 如果启用了 Telegram,ClawX 还会把代理同步到 OpenClaw 的 Telegram 频道配置中。
|
||||
- 当 ClawX 代理处于关闭状态时,Gateway 的常规重启会保留已有的 Telegram 频道代理配置。
|
||||
- 如果你要明确清空 OpenClaw 中的 Telegram 代理,请在关闭代理后点一次“保存代理设置”。
|
||||
- 在 **设置 → 高级 → 开发者** 中,可以直接运行 **OpenClaw Doctor**,执行 `openclaw doctor --json` 并在应用内查看诊断输出。
|
||||
- 在 Windows 打包版本中,内置的 `openclaw` CLI/TUI 会通过随包分发的 `node.exe` 入口运行,以保证终端输入行为稳定。
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'http';
|
||||
import { applyProxySettings } from '../../main/proxy';
|
||||
import { syncLaunchAtStartupSettingFromStore } from '../../main/launch-at-startup';
|
||||
import { syncProxyConfigToOpenClaw } from '../../utils/openclaw-proxy';
|
||||
import { getAllSettings, getSetting, resetSettings, setSetting, type AppSettings } from '../../utils/store';
|
||||
import type { HostApiContext } from '../context';
|
||||
import { parseJsonBody, sendJson } from '../route-utils';
|
||||
|
||||
async function handleProxySettingsChange(ctx: HostApiContext): Promise<void> {
|
||||
const settings = await getAllSettings();
|
||||
await syncProxyConfigToOpenClaw(settings, { preserveExistingWhenDisabled: false });
|
||||
await applyProxySettings(settings);
|
||||
if (ctx.gatewayManager.getStatus().state === 'running') {
|
||||
await ctx.gatewayManager.restart();
|
||||
|
||||
@@ -124,7 +124,7 @@ function ensureConfiguredPluginsUpgraded(configuredChannels: string[]): void {
|
||||
export async function syncGatewayConfigBeforeLaunch(
|
||||
appSettings: Awaited<ReturnType<typeof getAllSettings>>,
|
||||
): Promise<void> {
|
||||
await syncProxyConfigToOpenClaw(appSettings);
|
||||
await syncProxyConfigToOpenClaw(appSettings, { preserveExistingWhenDisabled: true });
|
||||
|
||||
try {
|
||||
await sanitizeOpenClawConfig();
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
saveProviderKeyToOpenClaw,
|
||||
removeProviderFromOpenClaw,
|
||||
} from '../utils/openclaw-auth';
|
||||
import { syncProxyConfigToOpenClaw } from '../utils/openclaw-proxy';
|
||||
import { buildOpenClawControlUiUrl } from '../utils/openclaw-control-ui';
|
||||
import { logger } from '../utils/logger';
|
||||
import {
|
||||
@@ -240,6 +241,7 @@ function registerUnifiedRequestHandlers(gatewayManager: GatewayManager): void {
|
||||
const providerService = getProviderService();
|
||||
const handleProxySettingsChange = async () => {
|
||||
const settings = await getAllSettings();
|
||||
await syncProxyConfigToOpenClaw(settings, { preserveExistingWhenDisabled: false });
|
||||
await applyProxySettings(settings);
|
||||
if (gatewayManager.getStatus().state === 'running') {
|
||||
await gatewayManager.restart();
|
||||
@@ -2121,6 +2123,7 @@ function registerAppHandlers(): void {
|
||||
function registerSettingsHandlers(gatewayManager: GatewayManager): void {
|
||||
const handleProxySettingsChange = async () => {
|
||||
const settings = await getAllSettings();
|
||||
await syncProxyConfigToOpenClaw(settings, { preserveExistingWhenDisabled: false });
|
||||
await applyProxySettings(settings);
|
||||
if (gatewayManager.getStatus().state === 'running') {
|
||||
await gatewayManager.restart();
|
||||
|
||||
@@ -3,11 +3,22 @@ import { resolveProxySettings, type ProxySettings } from './proxy';
|
||||
import { logger } from './logger';
|
||||
import { withConfigLock } from './config-mutex';
|
||||
|
||||
interface SyncProxyOptions {
|
||||
/**
|
||||
* When true, keep an existing channels.telegram.proxy value if proxy is
|
||||
* currently disabled in ClawX settings.
|
||||
*/
|
||||
preserveExistingWhenDisabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync ClawX global proxy settings into OpenClaw channel config where the
|
||||
* upstream runtime expects an explicit per-channel proxy knob.
|
||||
*/
|
||||
export async function syncProxyConfigToOpenClaw(settings: ProxySettings): Promise<void> {
|
||||
export async function syncProxyConfigToOpenClaw(
|
||||
settings: ProxySettings,
|
||||
options: SyncProxyOptions = {},
|
||||
): Promise<void> {
|
||||
return withConfigLock(async () => {
|
||||
const config = await readOpenClawConfig();
|
||||
const telegramConfig = config.channels?.telegram;
|
||||
@@ -17,11 +28,17 @@ export async function syncProxyConfigToOpenClaw(settings: ProxySettings): Promis
|
||||
}
|
||||
|
||||
const resolved = resolveProxySettings(settings);
|
||||
const preserveExistingWhenDisabled = options.preserveExistingWhenDisabled !== false;
|
||||
const nextProxy = settings.proxyEnabled
|
||||
? (resolved.allProxy || resolved.httpsProxy || resolved.httpProxy)
|
||||
: '';
|
||||
const currentProxy = typeof telegramConfig.proxy === 'string' ? telegramConfig.proxy : '';
|
||||
|
||||
if (!settings.proxyEnabled && preserveExistingWhenDisabled && currentProxy) {
|
||||
logger.info('Skipped Telegram proxy sync because ClawX proxy is disabled and preserve mode is enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nextProxy && !currentProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
"hideAdvancedProxy": "Hide advanced proxy fields",
|
||||
"proxyBypass": "Bypass Rules",
|
||||
"proxyBypassHelp": "Semicolon, comma, or newline separated hosts that should connect directly.",
|
||||
"proxyRestartNote": "Saving reapplies Electron networking and restarts the Gateway immediately.",
|
||||
"proxyRestartNote": "Saving reapplies Electron networking and restarts the Gateway immediately. Regular Gateway restarts keep existing Telegram channel proxy values when proxy is disabled; save while disabled to clear them.",
|
||||
"proxySaved": "Proxy settings saved",
|
||||
"proxySaveFailed": "Failed to save proxy settings"
|
||||
},
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
"hideAdvancedProxy": "高度なプロキシ項目を非表示",
|
||||
"proxyBypass": "バイパスルール",
|
||||
"proxyBypassHelp": "直接接続するホストをセミコロン、カンマ、または改行で区切って指定します。",
|
||||
"proxyRestartNote": "保存すると Electron のネットワーク設定を再適用し、Gateway をすぐ再起動します。",
|
||||
"proxyRestartNote": "保存すると Electron のネットワーク設定を再適用し、Gateway をすぐ再起動します。プロキシ無効時の通常再起動では Telegram チャネルの既存プロキシ設定を保持し、無効状態で保存すると明示的に削除されます。",
|
||||
"proxySaved": "プロキシ設定を保存しました",
|
||||
"proxySaveFailed": "プロキシ設定の保存に失敗しました"
|
||||
},
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
"hideAdvancedProxy": "隐藏高级代理字段",
|
||||
"proxyBypass": "绕过规则",
|
||||
"proxyBypassHelp": "使用分号、逗号或换行分隔需要直连的主机。",
|
||||
"proxyRestartNote": "保存后会立即重新应用 Electron 网络代理,并自动重启 Gateway。",
|
||||
"proxyRestartNote": "保存后会立即重新应用 Electron 网络代理,并自动重启 Gateway。若代理已关闭,Gateway 常规重启会保留 Telegram 频道已有代理;如需清空,请在关闭代理后手动保存一次。",
|
||||
"proxySaved": "代理设置已保存",
|
||||
"proxySaveFailed": "保存代理设置失败"
|
||||
},
|
||||
|
||||
89
tests/unit/openclaw-proxy.test.ts
Normal file
89
tests/unit/openclaw-proxy.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const {
|
||||
readOpenClawConfigMock,
|
||||
writeOpenClawConfigMock,
|
||||
withConfigLockMock,
|
||||
} = vi.hoisted(() => ({
|
||||
readOpenClawConfigMock: vi.fn(),
|
||||
writeOpenClawConfigMock: vi.fn(),
|
||||
withConfigLockMock: vi.fn(async (fn: () => Promise<unknown>) => await fn()),
|
||||
}));
|
||||
|
||||
vi.mock('@electron/utils/channel-config', () => ({
|
||||
readOpenClawConfig: readOpenClawConfigMock,
|
||||
writeOpenClawConfig: writeOpenClawConfigMock,
|
||||
}));
|
||||
|
||||
vi.mock('@electron/utils/config-mutex', () => ({
|
||||
withConfigLock: withConfigLockMock,
|
||||
}));
|
||||
|
||||
vi.mock('@electron/utils/logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('syncProxyConfigToOpenClaw', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('preserves existing telegram proxy on startup-style sync when proxy is disabled', async () => {
|
||||
readOpenClawConfigMock.mockResolvedValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: 'token',
|
||||
proxy: 'socks5://127.0.0.1:7891',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { syncProxyConfigToOpenClaw } = await import('@electron/utils/openclaw-proxy');
|
||||
|
||||
await syncProxyConfigToOpenClaw({
|
||||
proxyEnabled: false,
|
||||
proxyServer: '',
|
||||
proxyHttpServer: '',
|
||||
proxyHttpsServer: '',
|
||||
proxyAllServer: '',
|
||||
proxyBypassRules: '',
|
||||
});
|
||||
|
||||
expect(writeOpenClawConfigMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears telegram proxy when explicitly requested while proxy is disabled', async () => {
|
||||
readOpenClawConfigMock.mockResolvedValue({
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: 'token',
|
||||
proxy: 'socks5://127.0.0.1:7891',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { syncProxyConfigToOpenClaw } = await import('@electron/utils/openclaw-proxy');
|
||||
|
||||
await syncProxyConfigToOpenClaw({
|
||||
proxyEnabled: false,
|
||||
proxyServer: '',
|
||||
proxyHttpServer: '',
|
||||
proxyHttpsServer: '',
|
||||
proxyAllServer: '',
|
||||
proxyBypassRules: '',
|
||||
}, {
|
||||
preserveExistingWhenDisabled: false,
|
||||
});
|
||||
|
||||
expect(writeOpenClawConfigMock).toHaveBeenCalledTimes(1);
|
||||
const updatedConfig = writeOpenClawConfigMock.mock.calls[0][0] as {
|
||||
channels: { telegram: Record<string, unknown> };
|
||||
};
|
||||
expect(updatedConfig.channels.telegram.proxy).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user