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

@@ -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: {