upgrade openclaw to 3.23 (#652)

Co-authored-by: Felix <24791380+vcfgv@users.noreply.github.com>
This commit is contained in:
paisley
2026-03-26 16:58:04 +08:00
committed by GitHub
Unverified
parent b786b773f1
commit ba5947e2cb
22 changed files with 2927 additions and 4739 deletions

View File

@@ -158,6 +158,54 @@ describe('WeCom plugin configuration', () => {
expect(plugins.allow).toContain('wecom');
expect(plugins.entries['wecom'].enabled).toBe(true);
});
it('saves whatsapp as a built-in channel instead of a plugin', async () => {
const { saveChannelConfig } = await import('@electron/utils/channel-config');
await saveChannelConfig('whatsapp', { enabled: true }, 'default');
const config = await readOpenClawJson();
const channels = config.channels as Record<string, { enabled?: boolean; defaultAccount?: string; accounts?: Record<string, { enabled?: boolean }> }>;
expect(channels.whatsapp.enabled).toBe(true);
expect(channels.whatsapp.defaultAccount).toBe('default');
expect(channels.whatsapp.accounts?.default?.enabled).toBe(true);
expect(config.plugins).toBeUndefined();
});
it('cleans up stale whatsapp plugin registration when saving built-in config', async () => {
const { saveChannelConfig, writeOpenClawConfig } = await import('@electron/utils/channel-config');
await writeOpenClawConfig({
plugins: {
enabled: true,
allow: ['whatsapp'],
entries: {
whatsapp: { enabled: true },
},
},
});
await saveChannelConfig('whatsapp', { enabled: true }, 'default');
const config = await readOpenClawJson();
expect(config.plugins).toBeUndefined();
const channels = config.channels as Record<string, { enabled?: boolean }>;
expect(channels.whatsapp.enabled).toBe(true);
});
it('keeps configured built-in channels in plugins.allow when a plugin-backed channel is enabled', async () => {
const { saveChannelConfig } = await import('@electron/utils/channel-config');
await saveChannelConfig('discord', { token: 'discord-token' }, 'default');
await saveChannelConfig('whatsapp', { enabled: true }, 'default');
await saveChannelConfig('qqbot', { appId: 'qq-app', token: 'qq-token', appSecret: 'qq-secret' }, 'default');
const config = await readOpenClawJson();
const plugins = config.plugins as { allow: string[] };
expect(plugins.allow).toEqual(expect.arrayContaining(['qqbot', 'discord', 'whatsapp']));
});
});
describe('WeChat dangling plugin cleanup', () => {

View File

@@ -63,12 +63,29 @@ vi.mock('@electron/api/route-utils', () => ({
vi.mock('@electron/utils/paths', () => ({
getOpenClawConfigDir: () => testOpenClawConfigDir,
getOpenClawDir: () => testOpenClawConfigDir,
getOpenClawResolvedDir: () => testOpenClawConfigDir,
}));
vi.mock('@electron/utils/proxy-fetch', () => ({
proxyAwareFetch: (...args: unknown[]) => proxyAwareFetchMock(...args),
}));
// Stub openclaw SDK functions that are dynamically loaded via createRequire
// in the real code — the extracted utility module is easy to mock.
vi.mock('@electron/utils/openclaw-sdk', () => ({
listDiscordDirectoryGroupsFromConfig: vi.fn().mockResolvedValue([]),
listDiscordDirectoryPeersFromConfig: vi.fn().mockResolvedValue([]),
normalizeDiscordMessagingTarget: vi.fn().mockReturnValue(undefined),
listTelegramDirectoryGroupsFromConfig: vi.fn().mockResolvedValue([]),
listTelegramDirectoryPeersFromConfig: vi.fn().mockResolvedValue([]),
normalizeTelegramMessagingTarget: vi.fn().mockReturnValue(undefined),
listSlackDirectoryGroupsFromConfig: vi.fn().mockResolvedValue([]),
listSlackDirectoryPeersFromConfig: vi.fn().mockResolvedValue([]),
normalizeSlackMessagingTarget: vi.fn().mockReturnValue(undefined),
normalizeWhatsAppMessagingTarget: vi.fn().mockReturnValue(undefined),
}));
describe('handleChannelRoutes', () => {
beforeEach(() => {
vi.resetAllMocks();

View File

@@ -40,6 +40,19 @@ async function sanitizeConfig(filePath: string): Promise<boolean> {
const config = JSON.parse(raw) as Record<string, unknown>;
let modified = false;
const BUILTIN_CHANNEL_IDS = new Set([
'discord',
'telegram',
'whatsapp',
'slack',
'signal',
'imessage',
'matrix',
'line',
'msteams',
'googlechat',
'mattermost',
]);
/** Non-throwing async existence check. */
async function fileExists(p: string): Promise<boolean> {
@@ -104,6 +117,68 @@ async function sanitizeConfig(filePath: string): Promise<boolean> {
}
}
}
const allow = Array.isArray(pluginsObj.allow) ? [...pluginsObj.allow as string[]] : [];
const entries = (
pluginsObj.entries && typeof pluginsObj.entries === 'object' && !Array.isArray(pluginsObj.entries)
? { ...(pluginsObj.entries as Record<string, unknown>) }
: {}
) as Record<string, unknown>;
if ('whatsapp' in entries) {
delete entries.whatsapp;
pluginsObj.entries = entries;
modified = true;
}
const configuredBuiltIns = new Set<string>();
const channels = config.channels;
if (channels && typeof channels === 'object' && !Array.isArray(channels)) {
for (const [channelId, section] of Object.entries(channels as Record<string, Record<string, unknown>>)) {
if (!BUILTIN_CHANNEL_IDS.has(channelId)) continue;
if (!section || section.enabled === false) continue;
if (Object.keys(section).length > 0) {
configuredBuiltIns.add(channelId);
}
}
}
const externalPluginIds = allow.filter((id) => !BUILTIN_CHANNEL_IDS.has(id));
const nextAllow = [...externalPluginIds];
if (externalPluginIds.length > 0) {
for (const channelId of configuredBuiltIns) {
if (!nextAllow.includes(channelId)) {
nextAllow.push(channelId);
}
}
}
if (JSON.stringify(nextAllow) !== JSON.stringify(allow)) {
if (nextAllow.length > 0) {
pluginsObj.allow = nextAllow;
} else {
delete pluginsObj.allow;
}
modified = true;
}
if (Array.isArray(pluginsObj.allow) && pluginsObj.allow.length === 0) {
delete pluginsObj.allow;
modified = true;
}
if (pluginsObj.entries && Object.keys(entries).length === 0) {
delete pluginsObj.entries;
modified = true;
}
const pluginKeysExcludingEnabled = Object.keys(pluginsObj).filter((key) => key !== 'enabled');
if (pluginsObj.enabled === true && pluginKeysExcludingEnabled.length === 0) {
delete pluginsObj.enabled;
modified = true;
}
if (Object.keys(pluginsObj).length === 0) {
delete config.plugins;
modified = true;
}
}
// Mirror: remove stale tools.web.search.kimi.apiKey when moonshot provider exists.
@@ -275,7 +350,7 @@ describe('sanitizeOpenClawConfig (blocklist approach)', () => {
await writeConfig({
skills: { enabled: true, entries: {} },
channels: { discord: { token: 'abc', enabled: true } },
plugins: { entries: { whatsapp: { enabled: true } } },
plugins: { entries: { customPlugin: { enabled: true } } },
gateway: { mode: 'local', auth: { token: 'xyz' } },
agents: { defaults: { model: { primary: 'gpt-4' } } },
models: { providers: { openai: { baseUrl: 'https://api.openai.com' } } },
@@ -289,7 +364,7 @@ describe('sanitizeOpenClawConfig (blocklist approach)', () => {
expect(result.skills).not.toHaveProperty('enabled');
// All other sections unchanged
expect(result.channels).toEqual({ discord: { token: 'abc', enabled: true } });
expect(result.plugins).toEqual({ entries: { whatsapp: { enabled: true } } });
expect(result.plugins).toEqual({ entries: { customPlugin: { enabled: true } } });
expect(result.gateway).toEqual({ mode: 'local', auth: { token: 'xyz' } });
expect(result.agents).toEqual({ defaults: { model: { primary: 'gpt-4' } } });
});
@@ -359,7 +434,7 @@ describe('sanitizeOpenClawConfig (blocklist approach)', () => {
'/another/missing/plugin/dir',
],
},
entries: { whatsapp: { enabled: true } },
entries: { customPlugin: { enabled: true } },
},
gateway: { mode: 'local' },
});
@@ -372,11 +447,40 @@ describe('sanitizeOpenClawConfig (blocklist approach)', () => {
const load = plugins.load as Record<string, unknown>;
expect(load.paths).toEqual([]);
// Other plugin config is preserved
expect(plugins.entries).toEqual({ whatsapp: { enabled: true } });
expect(plugins.entries).toEqual({ customPlugin: { enabled: true } });
// Other top-level sections untouched
expect(result.gateway).toEqual({ mode: 'local' });
});
it('keeps configured built-in channels in plugins.allow when external plugins are enabled', async () => {
await writeConfig({
plugins: {
enabled: true,
allow: ['whatsapp', 'customPlugin'],
entries: {
whatsapp: { enabled: true },
customPlugin: { enabled: true },
},
},
channels: {
discord: { enabled: true, token: 'abc' },
},
});
const modified = await sanitizeConfig(configPath);
expect(modified).toBe(true);
const result = await readConfig();
expect(result.channels).toEqual({ discord: { enabled: true, token: 'abc' } });
expect(result.plugins).toEqual({
enabled: true,
allow: ['customPlugin', 'discord'],
entries: {
customPlugin: { enabled: true },
},
});
});
it('removes bundled node_modules paths from plugins.load.paths', async () => {
await writeConfig({
plugins: {