support webcom

This commit is contained in:
paisley
2026-03-09 21:53:14 +08:00
parent 3d664c017a
commit 37f4ea4a0d
12 changed files with 289 additions and 15 deletions

View File

@@ -57,6 +57,47 @@ async function ensureDingTalkPluginInstalled(): Promise<{ installed: boolean; wa
}
}
async function ensureWeComPluginInstalled(): Promise<{ installed: boolean; warning?: string }> {
const targetDir = join(homedir(), '.openclaw', 'extensions', 'wecom');
const targetManifest = join(targetDir, 'openclaw.plugin.json');
if (existsSync(targetManifest)) {
return { installed: true };
}
const candidateSources = app.isPackaged
? [
join(process.resourcesPath, 'openclaw-plugins', 'wecom'),
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', 'wecom'),
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', 'wecom'),
]
: [
join(app.getAppPath(), 'build', 'openclaw-plugins', 'wecom'),
join(process.cwd(), 'build', 'openclaw-plugins', 'wecom'),
join(__dirname, '../../../build/openclaw-plugins/wecom'),
];
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
if (!sourceDir) {
return {
installed: false,
warning: `Bundled WeCom plugin mirror not found. Checked: ${candidateSources.join(' | ')}`,
};
}
try {
mkdirSync(join(homedir(), '.openclaw', 'extensions'), { recursive: true });
rmSync(targetDir, { recursive: true, force: true });
cpSync(sourceDir, targetDir, { recursive: true, dereference: true });
if (!existsSync(targetManifest)) {
return { installed: false, warning: 'Failed to install WeCom plugin mirror (manifest missing).' };
}
return { installed: true };
} catch {
return { installed: false, warning: 'Failed to install bundled WeCom plugin mirror' };
}
}
export async function handleChannelRoutes(
req: IncomingMessage,
res: ServerResponse,
@@ -119,6 +160,13 @@ export async function handleChannelRoutes(
return true;
}
}
if (body.channelType === 'wecom') {
const installResult = await ensureWeComPluginInstalled();
if (!installResult.installed) {
sendJson(res, 500, { success: false, error: installResult.warning || 'WeCom plugin install failed' });
return true;
}
}
await saveChannelConfig(body.channelType, body.config);
sendJson(res, 200, { success: true });
} catch (error) {

View File

@@ -1373,6 +1373,56 @@ function registerOpenClawHandlers(gatewayManager: GatewayManager): void {
}
}
async function ensureWeComPluginInstalled(): Promise<{ installed: boolean; warning?: string }> {
const targetDir = join(homedir(), '.openclaw', 'extensions', 'wecom');
const targetManifest = join(targetDir, 'openclaw.plugin.json');
if (existsSync(targetManifest)) {
logger.info('WeCom plugin already installed from local mirror');
return { installed: true };
}
const candidateSources = app.isPackaged
? [
join(process.resourcesPath, 'openclaw-plugins', 'wecom'),
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', 'wecom'),
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', 'wecom')
]
: [
join(app.getAppPath(), 'build', 'openclaw-plugins', 'wecom'),
join(process.cwd(), 'build', 'openclaw-plugins', 'wecom'),
join(__dirname, '../../build/openclaw-plugins/wecom'),
];
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
if (!sourceDir) {
logger.warn('Bundled WeCom plugin mirror not found in candidate paths', { candidateSources });
return {
installed: false,
warning: `Bundled WeCom plugin mirror not found. Checked: ${candidateSources.join(' | ')}`,
};
}
try {
mkdirSync(join(homedir(), '.openclaw', 'extensions'), { recursive: true });
rmSync(targetDir, { recursive: true, force: true });
cpSync(sourceDir, targetDir, { recursive: true, dereference: true });
if (!existsSync(targetManifest)) {
return { installed: false, warning: 'Failed to install WeCom plugin mirror (manifest missing).' };
}
logger.info(`Installed WeCom plugin from bundled mirror: ${sourceDir}`);
return { installed: true };
} catch (error) {
logger.warn('Failed to install WeCom plugin from bundled mirror:', error);
return {
installed: false,
warning: 'Failed to install bundled WeCom plugin mirror',
};
}
}
// Get OpenClaw package status
ipcMain.handle('openclaw:status', () => {
const status = getOpenClawStatus();
@@ -1447,6 +1497,27 @@ function registerOpenClawHandlers(gatewayManager: GatewayManager): void {
warning: installResult.warning,
};
}
if (channelType === 'wecom') {
const installResult = await ensureWeComPluginInstalled();
if (!installResult.installed) {
return {
success: false,
error: installResult.warning || 'WeCom plugin install failed',
};
}
await saveChannelConfig(channelType, config);
if (gatewayManager.getStatus().state !== 'stopped') {
logger.info(`Scheduling Gateway reload after channel:saveConfig (${channelType})`);
gatewayManager.debouncedReload();
} else {
logger.info(`Gateway is stopped; skip immediate reload after channel:saveConfig (${channelType})`);
}
return {
success: true,
pluginInstalled: installResult.installed,
warning: installResult.warning,
};
}
await saveChannelConfig(channelType, config);
if (gatewayManager.getStatus().state !== 'stopped') {
logger.info(`Scheduling Gateway reload after channel:saveConfig (${channelType})`);

View File

@@ -14,6 +14,7 @@ import { proxyAwareFetch } from './proxy-fetch';
const OPENCLAW_DIR = join(homedir(), '.openclaw');
const CONFIG_FILE = join(OPENCLAW_DIR, 'openclaw.json');
const WECOM_PLUGIN_ID = 'wecom-openclaw-plugin';
// Channels that are managed as plugins (config goes under plugins.entries, not channels)
const PLUGIN_CHANNELS = ['whatsapp'];
@@ -33,6 +34,8 @@ export interface ChannelConfigData {
export interface PluginsConfig {
entries?: Record<string, ChannelConfigData>;
allow?: string[];
enabled?: boolean;
[key: string]: unknown;
}
@@ -99,15 +102,35 @@ export async function saveChannelConfig(
// DingTalk is a channel plugin; make sure it's explicitly allowed.
// Newer OpenClaw versions may not load non-bundled plugins when allowlist is empty.
if (channelType === 'dingtalk') {
const defaultDingtalkAllow = ['dingtalk'];
if (!currentConfig.plugins) {
currentConfig.plugins = {};
currentConfig.plugins = { allow: defaultDingtalkAllow, enabled: true };
} else {
currentConfig.plugins.enabled = true;
const allow: string[] = Array.isArray(currentConfig.plugins.allow)
? (currentConfig.plugins.allow as string[])
: [];
if (!allow.includes('dingtalk')) {
currentConfig.plugins.allow = [...allow, 'dingtalk'];
}
}
currentConfig.plugins.enabled = true;
const allow = Array.isArray(currentConfig.plugins.allow)
? currentConfig.plugins.allow as string[]
: [];
if (!allow.includes('dingtalk')) {
currentConfig.plugins.allow = [...allow, 'dingtalk'];
}
if (channelType === 'wecom') {
const defaultWecomAllow = [WECOM_PLUGIN_ID];
if (!currentConfig.plugins) {
currentConfig.plugins = { allow: defaultWecomAllow, enabled: true };
} else {
currentConfig.plugins.enabled = true;
const allow: string[] = Array.isArray(currentConfig.plugins.allow)
? (currentConfig.plugins.allow as string[])
: [];
const normalizedAllow = allow.filter((pluginId) => pluginId !== 'wecom');
if (!normalizedAllow.includes(WECOM_PLUGIN_ID)) {
currentConfig.plugins.allow = [...normalizedAllow, WECOM_PLUGIN_ID];
} else if (normalizedAllow.length !== allow.length) {
currentConfig.plugins.allow = normalizedAllow;
}
}
}
@@ -192,10 +215,11 @@ export async function saveChannelConfig(
}
}
// Special handling for Feishu: default to open DM policy with wildcard allowlist
if (channelType === 'feishu') {
// Special handling for Feishu / WeCom: default to open DM policy with wildcard allowlist
if (channelType === 'feishu' || channelType === 'wecom') {
const existingConfig = currentConfig.channels[channelType] || {};
transformedConfig.dmPolicy = transformedConfig.dmPolicy ?? existingConfig.dmPolicy ?? 'open';
const existingDmPolicy = existingConfig.dmPolicy === 'pairing' ? 'open' : existingConfig.dmPolicy;
transformedConfig.dmPolicy = transformedConfig.dmPolicy ?? existingDmPolicy ?? 'open';
let allowFrom = transformedConfig.allowFrom ?? existingConfig.allowFrom ?? ['*'];
if (!Array.isArray(allowFrom)) {