feat: support feishu official plugin (#418)

This commit is contained in:
paisley
2026-03-11 18:18:46 +08:00
committed by GitHub
Unverified
parent f37d2ac112
commit ce7e890509
9 changed files with 142 additions and 29 deletions

View File

@@ -75,15 +75,15 @@ async function ensureWeComPluginInstalled(): Promise<{ installed: boolean; warni
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(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'),
];
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) {
@@ -106,6 +106,47 @@ async function ensureWeComPluginInstalled(): Promise<{ installed: boolean; warni
}
}
async function ensureFeishuPluginInstalled(): Promise<{ installed: boolean; warning?: string }> {
const targetDir = join(homedir(), '.openclaw', 'extensions', 'feishu-openclaw-plugin');
const targetManifest = join(targetDir, 'openclaw.plugin.json');
if (existsSync(targetManifest)) {
return { installed: true };
}
const candidateSources = app.isPackaged
? [
join(process.resourcesPath, 'openclaw-plugins', 'feishu-openclaw-plugin'),
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', 'feishu-openclaw-plugin'),
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', 'feishu-openclaw-plugin'),
]
: [
join(app.getAppPath(), 'build', 'openclaw-plugins', 'feishu-openclaw-plugin'),
join(process.cwd(), 'build', 'openclaw-plugins', 'feishu-openclaw-plugin'),
join(__dirname, '../../../build/openclaw-plugins/feishu-openclaw-plugin'),
];
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
if (!sourceDir) {
return {
installed: false,
warning: `Bundled Feishu 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 Feishu plugin mirror (manifest missing).' };
}
return { installed: true };
} catch {
return { installed: false, warning: 'Failed to install bundled Feishu plugin mirror' };
}
}
async function ensureQQBotPluginInstalled(): Promise<{ installed: boolean; warning?: string }> {
const targetDir = join(homedir(), '.openclaw', 'extensions', 'qqbot');
const targetManifest = join(targetDir, 'openclaw.plugin.json');
@@ -116,15 +157,15 @@ async function ensureQQBotPluginInstalled(): Promise<{ installed: boolean; warni
const candidateSources = app.isPackaged
? [
join(process.resourcesPath, 'openclaw-plugins', 'qqbot'),
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', 'qqbot'),
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', 'qqbot'),
]
join(process.resourcesPath, 'openclaw-plugins', 'qqbot'),
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', 'qqbot'),
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', 'qqbot'),
]
: [
join(app.getAppPath(), 'build', 'openclaw-plugins', 'qqbot'),
join(process.cwd(), 'build', 'openclaw-plugins', 'qqbot'),
join(__dirname, '../../../build/openclaw-plugins/qqbot'),
];
join(app.getAppPath(), 'build', 'openclaw-plugins', 'qqbot'),
join(process.cwd(), 'build', 'openclaw-plugins', 'qqbot'),
join(__dirname, '../../../build/openclaw-plugins/qqbot'),
];
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
if (!sourceDir) {
@@ -223,6 +264,13 @@ export async function handleChannelRoutes(
return true;
}
}
if (body.channelType === 'feishu') {
const installResult = await ensureFeishuPluginInstalled();
if (!installResult.installed) {
sendJson(res, 500, { success: false, error: installResult.warning || 'Feishu plugin install failed' });
return true;
}
}
await saveChannelConfig(body.channelType, body.config);
scheduleGatewayChannelRestart(ctx, `channel:saveConfig:${body.channelType}`);
sendJson(res, 200, { success: true });

View File

@@ -99,6 +99,48 @@ export async function saveChannelConfig(
): Promise<void> {
const currentConfig = await readOpenClawConfig();
if (channelType === 'feishu') {
const FEISHU_PLUGIN_ID = 'feishu-openclaw-plugin';
if (!currentConfig.plugins) {
currentConfig.plugins = {
allow: [FEISHU_PLUGIN_ID],
enabled: true,
entries: {
feishu: { enabled: false },
[FEISHU_PLUGIN_ID]: { enabled: true }
}
};
} else {
currentConfig.plugins.enabled = true;
const allow: string[] = Array.isArray(currentConfig.plugins.allow)
? (currentConfig.plugins.allow as string[])
: [];
// Remove legacy 'feishu' plugin from allowlist
const normalizedAllow = allow.filter((pluginId) => pluginId !== 'feishu');
if (!normalizedAllow.includes(FEISHU_PLUGIN_ID)) {
currentConfig.plugins.allow = [...normalizedAllow, FEISHU_PLUGIN_ID];
} else if (normalizedAllow.length !== allow.length) {
currentConfig.plugins.allow = normalizedAllow;
}
// Explicitly disable the legacy plugin and enable the official one
if (!currentConfig.plugins.entries) {
currentConfig.plugins.entries = {};
}
if (!currentConfig.plugins.entries['feishu']) {
currentConfig.plugins.entries['feishu'] = {};
}
currentConfig.plugins.entries['feishu'].enabled = false;
if (!currentConfig.plugins.entries[FEISHU_PLUGIN_ID]) {
currentConfig.plugins.entries[FEISHU_PLUGIN_ID] = {};
}
currentConfig.plugins.entries[FEISHU_PLUGIN_ID].enabled = true;
}
}
// 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') {

View File

@@ -141,12 +141,7 @@ export async function getAllSkillConfigs(): Promise<Record<string, SkillEntry>>
* ~/.openclaw/skills/ on first launch. These come from the openclaw package's
* extensions directory and are available in both dev and packaged builds.
*/
const BUILTIN_SKILLS = [
{ slug: 'feishu-doc', sourceExtension: 'feishu' },
{ slug: 'feishu-drive', sourceExtension: 'feishu' },
{ slug: 'feishu-perm', sourceExtension: 'feishu' },
{ slug: 'feishu-wiki', sourceExtension: 'feishu' },
] as const;
const BUILTIN_SKILLS = [] as const;
/**
* Ensure built-in skills are deployed to ~/.openclaw/skills/<slug>/.