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は`プロキシサーバー`にフォールバックします。
|
- 高度なプロキシフィールドが空の場合、ClawXは`プロキシサーバー`にフォールバックします。
|
||||||
- プロキシ設定を保存すると、Electronのネットワーク設定が即座に再適用され、ゲートウェイが自動的に再起動されます。
|
- プロキシ設定を保存すると、Electronのネットワーク設定が即座に再適用され、ゲートウェイが自動的に再起動されます。
|
||||||
- ClawXはTelegramが有効な場合、プロキシをOpenClawのTelegramチャネル設定にも同期します。
|
- ClawXはTelegramが有効な場合、プロキシをOpenClawのTelegramチャネル設定にも同期します。
|
||||||
|
- ClawXのプロキシが無効な状態では、Gatewayの通常再起動時に既存のTelegramチャネルプロキシ設定を保持します。
|
||||||
|
- OpenClaw設定のTelegramプロキシを明示的に消したい場合は、プロキシ無効の状態で一度「保存」を実行してください。
|
||||||
- **設定 → 詳細 → 開発者** では **OpenClaw Doctor** を実行でき、`openclaw doctor --json` の診断出力をアプリ内で確認できます。
|
- **設定 → 詳細 → 開発者** では **OpenClaw Doctor** を実行でき、`openclaw doctor --json` の診断出力をアプリ内で確認できます。
|
||||||
- Windows のパッケージ版では、同梱された `openclaw` CLI/TUI は端末入力を安定させるため、同梱の `node.exe` エントリーポイント経由で実行されます。
|
- Windows のパッケージ版では、同梱された `openclaw` CLI/TUI は端末入力を安定させるため、同梱の `node.exe` エントリーポイント経由で実行されます。
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ Notes:
|
|||||||
- If advanced proxy fields are left empty, ClawX falls back to `Proxy Server`.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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。
|
- 保存代理设置后,Electron 网络层会立即重新应用代理,并自动重启 Gateway。
|
||||||
- 如果启用了 Telegram,ClawX 还会把代理同步到 OpenClaw 的 Telegram 频道配置中。
|
- 如果启用了 Telegram,ClawX 还会把代理同步到 OpenClaw 的 Telegram 频道配置中。
|
||||||
|
- 当 ClawX 代理处于关闭状态时,Gateway 的常规重启会保留已有的 Telegram 频道代理配置。
|
||||||
|
- 如果你要明确清空 OpenClaw 中的 Telegram 代理,请在关闭代理后点一次“保存代理设置”。
|
||||||
- 在 **设置 → 高级 → 开发者** 中,可以直接运行 **OpenClaw Doctor**,执行 `openclaw doctor --json` 并在应用内查看诊断输出。
|
- 在 **设置 → 高级 → 开发者** 中,可以直接运行 **OpenClaw Doctor**,执行 `openclaw doctor --json` 并在应用内查看诊断输出。
|
||||||
- 在 Windows 打包版本中,内置的 `openclaw` CLI/TUI 会通过随包分发的 `node.exe` 入口运行,以保证终端输入行为稳定。
|
- 在 Windows 打包版本中,内置的 `openclaw` CLI/TUI 会通过随包分发的 `node.exe` 入口运行,以保证终端输入行为稳定。
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from 'http';
|
import type { IncomingMessage, ServerResponse } from 'http';
|
||||||
import { applyProxySettings } from '../../main/proxy';
|
import { applyProxySettings } from '../../main/proxy';
|
||||||
import { syncLaunchAtStartupSettingFromStore } from '../../main/launch-at-startup';
|
import { syncLaunchAtStartupSettingFromStore } from '../../main/launch-at-startup';
|
||||||
|
import { syncProxyConfigToOpenClaw } from '../../utils/openclaw-proxy';
|
||||||
import { getAllSettings, getSetting, resetSettings, setSetting, type AppSettings } from '../../utils/store';
|
import { getAllSettings, getSetting, resetSettings, setSetting, type AppSettings } from '../../utils/store';
|
||||||
import type { HostApiContext } from '../context';
|
import type { HostApiContext } from '../context';
|
||||||
import { parseJsonBody, sendJson } from '../route-utils';
|
import { parseJsonBody, sendJson } from '../route-utils';
|
||||||
|
|
||||||
async function handleProxySettingsChange(ctx: HostApiContext): Promise<void> {
|
async function handleProxySettingsChange(ctx: HostApiContext): Promise<void> {
|
||||||
const settings = await getAllSettings();
|
const settings = await getAllSettings();
|
||||||
|
await syncProxyConfigToOpenClaw(settings, { preserveExistingWhenDisabled: false });
|
||||||
await applyProxySettings(settings);
|
await applyProxySettings(settings);
|
||||||
if (ctx.gatewayManager.getStatus().state === 'running') {
|
if (ctx.gatewayManager.getStatus().state === 'running') {
|
||||||
await ctx.gatewayManager.restart();
|
await ctx.gatewayManager.restart();
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function ensureConfiguredPluginsUpgraded(configuredChannels: string[]): void {
|
|||||||
export async function syncGatewayConfigBeforeLaunch(
|
export async function syncGatewayConfigBeforeLaunch(
|
||||||
appSettings: Awaited<ReturnType<typeof getAllSettings>>,
|
appSettings: Awaited<ReturnType<typeof getAllSettings>>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await syncProxyConfigToOpenClaw(appSettings);
|
await syncProxyConfigToOpenClaw(appSettings, { preserveExistingWhenDisabled: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sanitizeOpenClawConfig();
|
await sanitizeOpenClawConfig();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
saveProviderKeyToOpenClaw,
|
saveProviderKeyToOpenClaw,
|
||||||
removeProviderFromOpenClaw,
|
removeProviderFromOpenClaw,
|
||||||
} from '../utils/openclaw-auth';
|
} from '../utils/openclaw-auth';
|
||||||
|
import { syncProxyConfigToOpenClaw } from '../utils/openclaw-proxy';
|
||||||
import { buildOpenClawControlUiUrl } from '../utils/openclaw-control-ui';
|
import { buildOpenClawControlUiUrl } from '../utils/openclaw-control-ui';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
import {
|
import {
|
||||||
@@ -240,6 +241,7 @@ function registerUnifiedRequestHandlers(gatewayManager: GatewayManager): void {
|
|||||||
const providerService = getProviderService();
|
const providerService = getProviderService();
|
||||||
const handleProxySettingsChange = async () => {
|
const handleProxySettingsChange = async () => {
|
||||||
const settings = await getAllSettings();
|
const settings = await getAllSettings();
|
||||||
|
await syncProxyConfigToOpenClaw(settings, { preserveExistingWhenDisabled: false });
|
||||||
await applyProxySettings(settings);
|
await applyProxySettings(settings);
|
||||||
if (gatewayManager.getStatus().state === 'running') {
|
if (gatewayManager.getStatus().state === 'running') {
|
||||||
await gatewayManager.restart();
|
await gatewayManager.restart();
|
||||||
@@ -2121,6 +2123,7 @@ function registerAppHandlers(): void {
|
|||||||
function registerSettingsHandlers(gatewayManager: GatewayManager): void {
|
function registerSettingsHandlers(gatewayManager: GatewayManager): void {
|
||||||
const handleProxySettingsChange = async () => {
|
const handleProxySettingsChange = async () => {
|
||||||
const settings = await getAllSettings();
|
const settings = await getAllSettings();
|
||||||
|
await syncProxyConfigToOpenClaw(settings, { preserveExistingWhenDisabled: false });
|
||||||
await applyProxySettings(settings);
|
await applyProxySettings(settings);
|
||||||
if (gatewayManager.getStatus().state === 'running') {
|
if (gatewayManager.getStatus().state === 'running') {
|
||||||
await gatewayManager.restart();
|
await gatewayManager.restart();
|
||||||
|
|||||||
@@ -3,11 +3,22 @@ import { resolveProxySettings, type ProxySettings } from './proxy';
|
|||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import { withConfigLock } from './config-mutex';
|
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
|
* Sync ClawX global proxy settings into OpenClaw channel config where the
|
||||||
* upstream runtime expects an explicit per-channel proxy knob.
|
* 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 () => {
|
return withConfigLock(async () => {
|
||||||
const config = await readOpenClawConfig();
|
const config = await readOpenClawConfig();
|
||||||
const telegramConfig = config.channels?.telegram;
|
const telegramConfig = config.channels?.telegram;
|
||||||
@@ -17,11 +28,17 @@ export async function syncProxyConfigToOpenClaw(settings: ProxySettings): Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolved = resolveProxySettings(settings);
|
const resolved = resolveProxySettings(settings);
|
||||||
|
const preserveExistingWhenDisabled = options.preserveExistingWhenDisabled !== false;
|
||||||
const nextProxy = settings.proxyEnabled
|
const nextProxy = settings.proxyEnabled
|
||||||
? (resolved.allProxy || resolved.httpsProxy || resolved.httpProxy)
|
? (resolved.allProxy || resolved.httpsProxy || resolved.httpProxy)
|
||||||
: '';
|
: '';
|
||||||
const currentProxy = typeof telegramConfig.proxy === 'string' ? telegramConfig.proxy : '';
|
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) {
|
if (!nextProxy && !currentProxy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
"hideAdvancedProxy": "Hide advanced proxy fields",
|
"hideAdvancedProxy": "Hide advanced proxy fields",
|
||||||
"proxyBypass": "Bypass Rules",
|
"proxyBypass": "Bypass Rules",
|
||||||
"proxyBypassHelp": "Semicolon, comma, or newline separated hosts that should connect directly.",
|
"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",
|
"proxySaved": "Proxy settings saved",
|
||||||
"proxySaveFailed": "Failed to save proxy settings"
|
"proxySaveFailed": "Failed to save proxy settings"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
"hideAdvancedProxy": "高度なプロキシ項目を非表示",
|
"hideAdvancedProxy": "高度なプロキシ項目を非表示",
|
||||||
"proxyBypass": "バイパスルール",
|
"proxyBypass": "バイパスルール",
|
||||||
"proxyBypassHelp": "直接接続するホストをセミコロン、カンマ、または改行で区切って指定します。",
|
"proxyBypassHelp": "直接接続するホストをセミコロン、カンマ、または改行で区切って指定します。",
|
||||||
"proxyRestartNote": "保存すると Electron のネットワーク設定を再適用し、Gateway をすぐ再起動します。",
|
"proxyRestartNote": "保存すると Electron のネットワーク設定を再適用し、Gateway をすぐ再起動します。プロキシ無効時の通常再起動では Telegram チャネルの既存プロキシ設定を保持し、無効状態で保存すると明示的に削除されます。",
|
||||||
"proxySaved": "プロキシ設定を保存しました",
|
"proxySaved": "プロキシ設定を保存しました",
|
||||||
"proxySaveFailed": "プロキシ設定の保存に失敗しました"
|
"proxySaveFailed": "プロキシ設定の保存に失敗しました"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
"hideAdvancedProxy": "隐藏高级代理字段",
|
"hideAdvancedProxy": "隐藏高级代理字段",
|
||||||
"proxyBypass": "绕过规则",
|
"proxyBypass": "绕过规则",
|
||||||
"proxyBypassHelp": "使用分号、逗号或换行分隔需要直连的主机。",
|
"proxyBypassHelp": "使用分号、逗号或换行分隔需要直连的主机。",
|
||||||
"proxyRestartNote": "保存后会立即重新应用 Electron 网络代理,并自动重启 Gateway。",
|
"proxyRestartNote": "保存后会立即重新应用 Electron 网络代理,并自动重启 Gateway。若代理已关闭,Gateway 常规重启会保留 Telegram 频道已有代理;如需清空,请在关闭代理后手动保存一次。",
|
||||||
"proxySaved": "代理设置已保存",
|
"proxySaved": "代理设置已保存",
|
||||||
"proxySaveFailed": "保存代理设置失败"
|
"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