Upgrade openclaw to 4.1 (#742)
This commit is contained in:
committed by
GitHub
Unverified
parent
5a3da41562
commit
06266cb4d2
@@ -24,7 +24,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ensureDingTalkPluginInstalled,
|
ensureDingTalkPluginInstalled,
|
||||||
ensureFeishuPluginInstalled,
|
ensureFeishuPluginInstalled,
|
||||||
ensureQQBotPluginInstalled,
|
|
||||||
ensureWeChatPluginInstalled,
|
ensureWeChatPluginInstalled,
|
||||||
ensureWeComPluginInstalled,
|
ensureWeComPluginInstalled,
|
||||||
} from '../../utils/plugin-install';
|
} from '../../utils/plugin-install';
|
||||||
@@ -1198,13 +1197,7 @@ export async function handleChannelRoutes(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (storedChannelType === 'qqbot') {
|
// QQBot is a built-in channel since OpenClaw 3.31 — no plugin install needed
|
||||||
const installResult = await ensureQQBotPluginInstalled();
|
|
||||||
if (!installResult.installed) {
|
|
||||||
sendJson(res, 500, { success: false, error: installResult.warning || 'QQ Bot plugin install failed' });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (storedChannelType === 'feishu') {
|
if (storedChannelType === 'feishu') {
|
||||||
const installResult = await ensureFeishuPluginInstalled();
|
const installResult = await ensureFeishuPluginInstalled();
|
||||||
if (!installResult.installed) {
|
if (!installResult.installed) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const CHANNEL_PLUGIN_MAP: Record<string, { dirName: string; npmName: string }> =
|
|||||||
dingtalk: { dirName: 'dingtalk', npmName: '@soimy/dingtalk' },
|
dingtalk: { dirName: 'dingtalk', npmName: '@soimy/dingtalk' },
|
||||||
wecom: { dirName: 'wecom', npmName: '@wecom/wecom-openclaw-plugin' },
|
wecom: { dirName: 'wecom', npmName: '@wecom/wecom-openclaw-plugin' },
|
||||||
feishu: { dirName: 'feishu-openclaw-plugin', npmName: '@larksuite/openclaw-lark' },
|
feishu: { dirName: 'feishu-openclaw-plugin', npmName: '@larksuite/openclaw-lark' },
|
||||||
qqbot: { dirName: 'qqbot', npmName: '@tencent-connect/openclaw-qqbot' },
|
|
||||||
'openclaw-weixin': { dirName: 'openclaw-weixin', npmName: '@tencent-weixin/openclaw-weixin' },
|
'openclaw-weixin': { dirName: 'openclaw-weixin', npmName: '@tencent-weixin/openclaw-weixin' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ const CHANNEL_PLUGIN_MAP: Record<string, { dirName: string; npmName: string }> =
|
|||||||
* ~/.openclaw/extensions/, the broken copy overrides the working built-in
|
* ~/.openclaw/extensions/, the broken copy overrides the working built-in
|
||||||
* plugin and must be removed.
|
* plugin and must be removed.
|
||||||
*/
|
*/
|
||||||
const BUILTIN_CHANNEL_EXTENSIONS = ['discord', 'telegram'];
|
const BUILTIN_CHANNEL_EXTENSIONS = ['discord', 'telegram', 'qqbot'];
|
||||||
|
|
||||||
function cleanupStaleBuiltInExtensions(): void {
|
function cleanupStaleBuiltInExtensions(): void {
|
||||||
for (const ext of BUILTIN_CHANNEL_EXTENSIONS) {
|
for (const ext of BUILTIN_CHANNEL_EXTENSIONS) {
|
||||||
@@ -208,7 +208,7 @@ export async function syncGatewayConfigBeforeLaunch(
|
|||||||
pluginIdToChannel[info.dirName] = channelType;
|
pluginIdToChannel[info.dirName] = channelType;
|
||||||
}
|
}
|
||||||
// Known manifest IDs that differ from their dirName/channelType
|
// Known manifest IDs that differ from their dirName/channelType
|
||||||
pluginIdToChannel['openclaw-qqbot'] = 'qqbot';
|
|
||||||
pluginIdToChannel['openclaw-lark'] = 'feishu';
|
pluginIdToChannel['openclaw-lark'] = 'feishu';
|
||||||
pluginIdToChannel['feishu-openclaw-plugin'] = 'feishu';
|
pluginIdToChannel['feishu-openclaw-plugin'] = 'feishu';
|
||||||
|
|
||||||
|
|||||||
@@ -372,8 +372,9 @@ async function initialize(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-deploy/upgrade bundled OpenClaw plugins (dingtalk, wecom, qqbot, feishu, wechat)
|
// Pre-deploy/upgrade bundled OpenClaw plugins (dingtalk, wecom, feishu, wechat)
|
||||||
// to ~/.openclaw/extensions/ so they are always up-to-date after an app update.
|
// to ~/.openclaw/extensions/ so they are always up-to-date after an app update.
|
||||||
|
// Note: qqbot was moved to a built-in channel in OpenClaw 3.31.
|
||||||
if (!isE2EMode) {
|
if (!isE2EMode) {
|
||||||
void ensureAllBundledPluginsInstalled().catch((error) => {
|
void ensureAllBundledPluginsInstalled().catch((error) => {
|
||||||
logger.warn('Failed to install/upgrade bundled plugins:', error);
|
logger.warn('Failed to install/upgrade bundled plugins:', error);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import { checkUvInstalled, installUv, setupManagedPython } from '../utils/uv-set
|
|||||||
import {
|
import {
|
||||||
ensureDingTalkPluginInstalled,
|
ensureDingTalkPluginInstalled,
|
||||||
ensureFeishuPluginInstalled,
|
ensureFeishuPluginInstalled,
|
||||||
ensureQQBotPluginInstalled,
|
|
||||||
ensureWeComPluginInstalled,
|
ensureWeComPluginInstalled,
|
||||||
} from '../utils/plugin-install';
|
} from '../utils/plugin-install';
|
||||||
import { updateSkillConfig, getSkillConfig, getAllSkillConfigs } from '../utils/skill-config';
|
import { updateSkillConfig, getSkillConfig, getAllSkillConfigs } from '../utils/skill-config';
|
||||||
@@ -1498,22 +1497,7 @@ function registerOpenClawHandlers(gatewayManager: GatewayManager): void {
|
|||||||
warning: installResult.warning,
|
warning: installResult.warning,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (channelType === 'qqbot') {
|
// QQBot is a built-in channel since OpenClaw 3.31 — no plugin install needed
|
||||||
const installResult = await ensureQQBotPluginInstalled();
|
|
||||||
if (!installResult.installed) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: installResult.warning || 'QQ Bot plugin install failed',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
await saveChannelConfig(channelType, config);
|
|
||||||
scheduleGatewayChannelSaveRefresh(channelType, `channel:saveConfig (${channelType})`);
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
pluginInstalled: installResult.installed,
|
|
||||||
warning: installResult.warning,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (channelType === 'feishu') {
|
if (channelType === 'feishu') {
|
||||||
const installResult = await ensureFeishuPluginInstalled();
|
const installResult = await ensureFeishuPluginInstalled();
|
||||||
if (!installResult.installed) {
|
if (!installResult.installed) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
const OPENCLAW_DIR = join(homedir(), '.openclaw');
|
const OPENCLAW_DIR = join(homedir(), '.openclaw');
|
||||||
const CONFIG_FILE = join(OPENCLAW_DIR, 'openclaw.json');
|
const CONFIG_FILE = join(OPENCLAW_DIR, 'openclaw.json');
|
||||||
const WECOM_PLUGIN_ID = 'wecom';
|
const WECOM_PLUGIN_ID = 'wecom';
|
||||||
const QQBOT_PLUGIN_ID = 'openclaw-qqbot';
|
// Note: QQBot is a built-in channel since OpenClaw 3.31 — no plugin ID needed.
|
||||||
const WECHAT_PLUGIN_ID = OPENCLAW_WECHAT_CHANNEL_TYPE;
|
const WECHAT_PLUGIN_ID = OPENCLAW_WECHAT_CHANNEL_TYPE;
|
||||||
const FEISHU_PLUGIN_ID_CANDIDATES = ['openclaw-lark', 'feishu-openclaw-plugin'] as const;
|
const FEISHU_PLUGIN_ID_CANDIDATES = ['openclaw-lark', 'feishu-openclaw-plugin'] as const;
|
||||||
const DEFAULT_ACCOUNT_ID = 'default';
|
const DEFAULT_ACCOUNT_ID = 'default';
|
||||||
@@ -48,6 +48,7 @@ const BUILTIN_CHANNEL_IDS = new Set([
|
|||||||
'msteams',
|
'msteams',
|
||||||
'googlechat',
|
'googlechat',
|
||||||
'mattermost',
|
'mattermost',
|
||||||
|
'qqbot',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Unique credential key per channel type – used for duplicate bot detection.
|
// Unique credential key per channel type – used for duplicate bot detection.
|
||||||
@@ -464,37 +465,7 @@ async function ensurePluginAllowlist(currentConfig: OpenClawConfig, channelType:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelType === 'qqbot') {
|
// Note: QQBot is a built-in channel since OpenClaw 3.31 — no plugin registration needed.
|
||||||
if (!currentConfig.plugins) {
|
|
||||||
currentConfig.plugins = {
|
|
||||||
allow: [QQBOT_PLUGIN_ID],
|
|
||||||
enabled: true,
|
|
||||||
entries: {
|
|
||||||
[QQBOT_PLUGIN_ID]: { enabled: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
currentConfig.plugins.enabled = true;
|
|
||||||
const allow: string[] = Array.isArray(currentConfig.plugins.allow)
|
|
||||||
? (currentConfig.plugins.allow as string[])
|
|
||||||
: [];
|
|
||||||
// Normalize: remove bare 'qqbot' and ensure the actual manifest ID is present.
|
|
||||||
const normalizedAllow = allow.filter((pluginId) => pluginId !== 'qqbot');
|
|
||||||
if (!normalizedAllow.includes(QQBOT_PLUGIN_ID)) {
|
|
||||||
currentConfig.plugins.allow = [...normalizedAllow, QQBOT_PLUGIN_ID];
|
|
||||||
} else if (normalizedAllow.length !== allow.length) {
|
|
||||||
currentConfig.plugins.allow = normalizedAllow;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentConfig.plugins.entries) {
|
|
||||||
currentConfig.plugins.entries = {};
|
|
||||||
}
|
|
||||||
if (!currentConfig.plugins.entries[QQBOT_PLUGIN_ID]) {
|
|
||||||
currentConfig.plugins.entries[QQBOT_PLUGIN_ID] = {};
|
|
||||||
}
|
|
||||||
currentConfig.plugins.entries[QQBOT_PLUGIN_ID].enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channelType === WECHAT_PLUGIN_ID) {
|
if (channelType === WECHAT_PLUGIN_ID) {
|
||||||
if (!currentConfig.plugins) {
|
if (!currentConfig.plugins) {
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ const BUILTIN_CHANNEL_IDS = new Set([
|
|||||||
'msteams',
|
'msteams',
|
||||||
'googlechat',
|
'googlechat',
|
||||||
'mattermost',
|
'mattermost',
|
||||||
|
'qqbot',
|
||||||
]);
|
]);
|
||||||
const AUTH_PROFILE_PROVIDER_KEY_MAP: Record<string, string> = {
|
const AUTH_PROFILE_PROVIDER_KEY_MAP: Record<string, string> = {
|
||||||
'openai-codex': 'openai',
|
'openai-codex': 'openai',
|
||||||
@@ -1507,34 +1508,21 @@ export async function sanitizeOpenClawConfig(): Promise<void> {
|
|||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── qqbot → openclaw-qqbot migration ────────────────────────
|
// ── qqbot built-in channel cleanup ──────────────────────────
|
||||||
// The qqbot npm package (@tencent-connect/openclaw-qqbot) declares
|
// OpenClaw 3.31 moved qqbot from a third-party plugin to a built-in
|
||||||
// id="openclaw-qqbot" in its manifest, but older ClawX versions
|
// channel. Clean up legacy plugin entries (both bare "qqbot" and
|
||||||
// wrote bare "qqbot" into plugins.allow. Migrate to the manifest ID
|
// manifest-declared "openclaw-qqbot") from plugins.entries.
|
||||||
// so the Gateway can resolve the plugin correctly.
|
// plugins.allow is left untouched — having openclaw-qqbot there is harmless.
|
||||||
const LEGACY_QQBOT_ID = 'qqbot';
|
// The channel config under channels.qqbot is preserved and works
|
||||||
const NEW_QQBOT_ID = 'openclaw-qqbot';
|
// identically with the built-in channel.
|
||||||
if (Array.isArray(pluginsObj.allow)) {
|
const QQBOT_PLUGIN_IDS = ['qqbot', 'openclaw-qqbot'] as const;
|
||||||
const allowArr = pluginsObj.allow as string[];
|
for (const qqbotId of QQBOT_PLUGIN_IDS) {
|
||||||
const legacyIdx = allowArr.indexOf(LEGACY_QQBOT_ID);
|
if (pEntries?.[qqbotId]) {
|
||||||
if (legacyIdx !== -1) {
|
delete pEntries[qqbotId];
|
||||||
if (!allowArr.includes(NEW_QQBOT_ID)) {
|
console.log(`[sanitize] Removed built-in channel plugin from plugins.entries: ${qqbotId}`);
|
||||||
allowArr[legacyIdx] = NEW_QQBOT_ID;
|
|
||||||
} else {
|
|
||||||
allowArr.splice(legacyIdx, 1);
|
|
||||||
}
|
|
||||||
console.log(`[sanitize] Migrated plugins.allow: ${LEGACY_QQBOT_ID} → ${NEW_QQBOT_ID}`);
|
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pEntries?.[LEGACY_QQBOT_ID]) {
|
|
||||||
if (!pEntries[NEW_QQBOT_ID]) {
|
|
||||||
pEntries[NEW_QQBOT_ID] = pEntries[LEGACY_QQBOT_ID];
|
|
||||||
}
|
|
||||||
delete pEntries[LEGACY_QQBOT_ID];
|
|
||||||
console.log(`[sanitize] Migrated plugins.entries: ${LEGACY_QQBOT_ID} → ${NEW_QQBOT_ID}`);
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── qwen-portal → modelstudio migration ────────────────────
|
// ── qwen-portal → modelstudio migration ────────────────────
|
||||||
// OpenClaw 2026.3.28 deprecated qwen-portal OAuth (portal.qwen.ai)
|
// OpenClaw 2026.3.28 deprecated qwen-portal OAuth (portal.qwen.ai)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const _discordSdk = requireOpenClawSdk('openclaw/plugin-sdk/discord') as {
|
|||||||
normalizeDiscordMessagingTarget: (target: string) => string | undefined;
|
normalizeDiscordMessagingTarget: (target: string) => string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _telegramSdk = requireOpenClawSdk('openclaw/plugin-sdk/telegram') as {
|
const _telegramSdk = requireOpenClawSdk('openclaw/plugin-sdk/telegram-surface') as {
|
||||||
listTelegramDirectoryGroupsFromConfig: (...args: unknown[]) => Promise<unknown[]>;
|
listTelegramDirectoryGroupsFromConfig: (...args: unknown[]) => Promise<unknown[]>;
|
||||||
listTelegramDirectoryPeersFromConfig: (...args: unknown[]) => Promise<unknown[]>;
|
listTelegramDirectoryPeersFromConfig: (...args: unknown[]) => Promise<unknown[]>;
|
||||||
normalizeTelegramMessagingTarget: (target: string) => string | undefined;
|
normalizeTelegramMessagingTarget: (target: string) => string | undefined;
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
* Shared OpenClaw Plugin Install Utilities
|
* Shared OpenClaw Plugin Install Utilities
|
||||||
*
|
*
|
||||||
* Provides version-aware install/upgrade logic for bundled OpenClaw plugins
|
* Provides version-aware install/upgrade logic for bundled OpenClaw plugins
|
||||||
* (DingTalk, WeCom, QQBot, Feishu, WeChat). Used both at app startup (to auto-upgrade
|
* (DingTalk, WeCom, Feishu, WeChat). Used both at app startup (to auto-upgrade
|
||||||
* stale plugins) and when a user configures a channel.
|
* stale plugins) and when a user configures a channel.
|
||||||
|
*
|
||||||
|
* Note: QQBot was moved to a built-in channel in OpenClaw 3.31 and is no longer
|
||||||
|
* managed as a plugin.
|
||||||
*/
|
*/
|
||||||
import { app } from 'electron';
|
import { app } from 'electron';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -231,7 +234,7 @@ const PLUGIN_NPM_NAMES: Record<string, string> = {
|
|||||||
dingtalk: '@soimy/dingtalk',
|
dingtalk: '@soimy/dingtalk',
|
||||||
wecom: '@wecom/wecom-openclaw-plugin',
|
wecom: '@wecom/wecom-openclaw-plugin',
|
||||||
'feishu-openclaw-plugin': '@larksuite/openclaw-lark',
|
'feishu-openclaw-plugin': '@larksuite/openclaw-lark',
|
||||||
qqbot: '@tencent-connect/openclaw-qqbot',
|
|
||||||
'openclaw-weixin': '@tencent-weixin/openclaw-weixin',
|
'openclaw-weixin': '@tencent-weixin/openclaw-weixin',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -508,9 +511,7 @@ export function ensureFeishuPluginInstalled(): { installed: boolean; warning?: s
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureQQBotPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('qqbot', buildCandidateSources('qqbot'), 'QQ Bot');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensureWeChatPluginInstalled(): { installed: boolean; warning?: string } {
|
export function ensureWeChatPluginInstalled(): { installed: boolean; warning?: string } {
|
||||||
return ensurePluginInstalled('openclaw-weixin', buildCandidateSources('openclaw-weixin'), 'WeChat');
|
return ensurePluginInstalled('openclaw-weixin', buildCandidateSources('openclaw-weixin'), 'WeChat');
|
||||||
@@ -524,7 +525,7 @@ export function ensureWeChatPluginInstalled(): { installed: boolean; warning?: s
|
|||||||
const ALL_BUNDLED_PLUGINS = [
|
const ALL_BUNDLED_PLUGINS = [
|
||||||
{ fn: ensureDingTalkPluginInstalled, label: 'DingTalk' },
|
{ fn: ensureDingTalkPluginInstalled, label: 'DingTalk' },
|
||||||
{ fn: ensureWeComPluginInstalled, label: 'WeCom' },
|
{ fn: ensureWeComPluginInstalled, label: 'WeCom' },
|
||||||
{ fn: ensureQQBotPluginInstalled, label: 'QQ Bot' },
|
|
||||||
{ fn: ensureFeishuPluginInstalled, label: 'Feishu' },
|
{ fn: ensureFeishuPluginInstalled, label: 'Feishu' },
|
||||||
{ fn: ensureWeChatPluginInstalled, label: 'WeChat' },
|
{ fn: ensureWeChatPluginInstalled, label: 'WeChat' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawx",
|
"name": "clawx",
|
||||||
"version": "0.3.4",
|
"version": "0.3.5-beta.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@discordjs/opus",
|
"@discordjs/opus",
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@larksuite/openclaw-lark": "2026.3.30",
|
"@larksuite/openclaw-lark": "2026.3.31",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
@@ -94,7 +94,6 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@playwright/test": "^1.56.1",
|
"@playwright/test": "^1.56.1",
|
||||||
"@soimy/dingtalk": "^3.5.1",
|
"@soimy/dingtalk": "^3.5.1",
|
||||||
"@tencent-connect/openclaw-qqbot": "^1.6.7",
|
|
||||||
"@tencent-weixin/openclaw-weixin": "^2.1.1",
|
"@tencent-weixin/openclaw-weixin": "^2.1.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
"i18next": "^25.8.11",
|
"i18next": "^25.8.11",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
"lucide-react": "^0.563.0",
|
"lucide-react": "^0.563.0",
|
||||||
"openclaw": "2026.3.28",
|
"openclaw": "2026.4.1",
|
||||||
"png2icons": "^2.0.1",
|
"png2icons": "^2.0.1",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
|
|||||||
679
pnpm-lock.yaml
generated
679
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -575,7 +575,6 @@ exports.default = async function afterPack(context) {
|
|||||||
const BUNDLED_PLUGINS = [
|
const BUNDLED_PLUGINS = [
|
||||||
{ npmName: '@soimy/dingtalk', pluginId: 'dingtalk' },
|
{ npmName: '@soimy/dingtalk', pluginId: 'dingtalk' },
|
||||||
{ npmName: '@wecom/wecom-openclaw-plugin', pluginId: 'wecom' },
|
{ npmName: '@wecom/wecom-openclaw-plugin', pluginId: 'wecom' },
|
||||||
{ npmName: '@tencent-connect/openclaw-qqbot', pluginId: 'qqbot' },
|
|
||||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'feishu-openclaw-plugin' },
|
{ npmName: '@larksuite/openclaw-lark', pluginId: 'feishu-openclaw-plugin' },
|
||||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||||
];
|
];
|
||||||
@@ -597,6 +596,63 @@ exports.default = async function afterPack(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.2 Copy built-in extension node_modules that electron-builder skipped.
|
||||||
|
// OpenClaw 3.31+ ships built-in extensions (discord, qqbot, etc.) under
|
||||||
|
// dist/extensions/<ext>/node_modules/. These are skipped by extraResources
|
||||||
|
// because .gitignore contains "node_modules/".
|
||||||
|
//
|
||||||
|
// Extension code is loaded via shared chunks in dist/ (e.g. outbound-*.js)
|
||||||
|
// which resolve modules from the top-level openclaw/node_modules/, NOT from
|
||||||
|
// the extension's own node_modules/. So we must merge extension deps into
|
||||||
|
// the top-level node_modules/ as well.
|
||||||
|
const buildExtDir = join(__dirname, '..', 'build', 'openclaw', 'dist', 'extensions');
|
||||||
|
const packExtDir = join(openclawRoot, 'dist', 'extensions');
|
||||||
|
if (existsSync(buildExtDir)) {
|
||||||
|
let extNMCount = 0;
|
||||||
|
let mergedPkgCount = 0;
|
||||||
|
for (const extEntry of readdirSync(buildExtDir, { withFileTypes: true })) {
|
||||||
|
if (!extEntry.isDirectory()) continue;
|
||||||
|
const srcNM = join(buildExtDir, extEntry.name, 'node_modules');
|
||||||
|
if (!existsSync(srcNM)) continue;
|
||||||
|
|
||||||
|
// Copy to extension's own node_modules (for direct requires from extension code)
|
||||||
|
const destExtNM = join(packExtDir, extEntry.name, 'node_modules');
|
||||||
|
if (!existsSync(destExtNM)) {
|
||||||
|
cpSync(srcNM, destExtNM, { recursive: true });
|
||||||
|
}
|
||||||
|
extNMCount++;
|
||||||
|
|
||||||
|
// Merge into top-level openclaw/node_modules/ (for shared chunks in dist/)
|
||||||
|
for (const pkgEntry of readdirSync(srcNM, { withFileTypes: true })) {
|
||||||
|
if (!pkgEntry.isDirectory() || pkgEntry.name === '.bin') continue;
|
||||||
|
const srcPkg = join(srcNM, pkgEntry.name);
|
||||||
|
const destPkg = join(dest, pkgEntry.name);
|
||||||
|
|
||||||
|
if (pkgEntry.name.startsWith('@')) {
|
||||||
|
// Scoped package — iterate sub-entries
|
||||||
|
for (const scopeEntry of readdirSync(srcPkg, { withFileTypes: true })) {
|
||||||
|
if (!scopeEntry.isDirectory()) continue;
|
||||||
|
const srcScoped = join(srcPkg, scopeEntry.name);
|
||||||
|
const destScoped = join(destPkg, scopeEntry.name);
|
||||||
|
if (!existsSync(destScoped)) {
|
||||||
|
mkdirSync(dirname(destScoped), { recursive: true });
|
||||||
|
cpSync(srcScoped, destScoped, { recursive: true });
|
||||||
|
mergedPkgCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!existsSync(destPkg)) {
|
||||||
|
cpSync(srcPkg, destPkg, { recursive: true });
|
||||||
|
mergedPkgCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extNMCount > 0) {
|
||||||
|
console.log(`[after-pack] ✅ Copied node_modules for ${extNMCount} built-in extension(s), merged ${mergedPkgCount} packages into top-level.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. General cleanup on the full openclaw directory (not just node_modules)
|
// 2. General cleanup on the full openclaw directory (not just node_modules)
|
||||||
console.log('[after-pack] 🧹 Cleaning up unnecessary files ...');
|
console.log('[after-pack] 🧹 Cleaning up unnecessary files ...');
|
||||||
const removedRoot = cleanupUnnecessaryFiles(openclawRoot);
|
const removedRoot = cleanupUnnecessaryFiles(openclawRoot);
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ function normWin(p) {
|
|||||||
const PLUGINS = [
|
const PLUGINS = [
|
||||||
{ npmName: '@soimy/dingtalk', pluginId: 'dingtalk' },
|
{ npmName: '@soimy/dingtalk', pluginId: 'dingtalk' },
|
||||||
{ npmName: '@wecom/wecom-openclaw-plugin', pluginId: 'wecom' },
|
{ npmName: '@wecom/wecom-openclaw-plugin', pluginId: 'wecom' },
|
||||||
{ npmName: '@tencent-connect/openclaw-qqbot', pluginId: 'qqbot' },
|
|
||||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'feishu-openclaw-plugin' },
|
{ npmName: '@larksuite/openclaw-lark', pluginId: 'feishu-openclaw-plugin' },
|
||||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -458,34 +458,7 @@ function patchBrokenModules(nodeModulesDir) {
|
|||||||
].join('\n'),
|
].join('\n'),
|
||||||
};
|
};
|
||||||
const replacePatches = [
|
const replacePatches = [
|
||||||
{
|
// Note: @mariozechner/pi-coding-agent is no longer a dep of openclaw 3.31.
|
||||||
rel: '@mariozechner/pi-coding-agent/dist/core/bash-executor.js',
|
|
||||||
search: ` const child = spawn(shell, [...args, command], {
|
|
||||||
detached: true,
|
|
||||||
env: getShellEnv(),
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
});`,
|
|
||||||
replace: ` const child = spawn(shell, [...args, command], {
|
|
||||||
detached: true,
|
|
||||||
env: getShellEnv(),
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
windowsHide: true,
|
|
||||||
});`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: '@mariozechner/pi-coding-agent/dist/core/exec.js',
|
|
||||||
search: ` const proc = spawn(command, args, {
|
|
||||||
cwd,
|
|
||||||
shell: false,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
});`,
|
|
||||||
replace: ` const proc = spawn(command, args, {
|
|
||||||
cwd,
|
|
||||||
shell: false,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
windowsHide: true,
|
|
||||||
});`,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@@ -671,76 +644,9 @@ function patchBundledRuntime(outputDir) {
|
|||||||
\t\t}) ? { shell: true } : {}
|
\t\t}) ? { shell: true } : {}
|
||||||
\t});`,
|
\t});`,
|
||||||
},
|
},
|
||||||
{
|
// Note: OpenClaw 3.31 removed the hash-suffixed agent-scope-*.js, chrome-*.js,
|
||||||
label: 'agent scope command runner',
|
// and qmd-manager-*.js files from dist/plugin-sdk/. Patches for those spawn
|
||||||
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^agent-scope-.*\.js$/),
|
// sites are no longer needed — the runtime now uses windowsHide natively.
|
||||||
search: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
|
|
||||||
\t\tstdio,
|
|
||||||
\t\tcwd,
|
|
||||||
\t\tenv: resolvedEnv,
|
|
||||||
\t\twindowsVerbatimArguments,
|
|
||||||
\t\t...shouldSpawnWithShell({
|
|
||||||
\t\t\tresolvedCommand,
|
|
||||||
\t\t\tplatform: process$1.platform
|
|
||||||
\t\t}) ? { shell: true } : {}
|
|
||||||
\t});`,
|
|
||||||
replace: `\tconst child = spawn(resolvedCommand, finalArgv.slice(1), {
|
|
||||||
\t\tstdio,
|
|
||||||
\t\tcwd,
|
|
||||||
\t\tenv: resolvedEnv,
|
|
||||||
\t\twindowsVerbatimArguments,
|
|
||||||
\t\twindowsHide: true,
|
|
||||||
\t\t...shouldSpawnWithShell({
|
|
||||||
\t\t\tresolvedCommand,
|
|
||||||
\t\t\tplatform: process$1.platform
|
|
||||||
\t\t}) ? { shell: true } : {}
|
|
||||||
\t});`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'chrome launcher',
|
|
||||||
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^chrome-.*\.js$/),
|
|
||||||
search: `\t\treturn spawn(exe.path, args, {
|
|
||||||
\t\t\tstdio: "pipe",
|
|
||||||
\t\t\tenv: {
|
|
||||||
\t\t\t\t...process.env,
|
|
||||||
\t\t\t\tHOME: os.homedir()
|
|
||||||
\t\t\t}
|
|
||||||
\t\t});`,
|
|
||||||
replace: `\t\treturn spawn(exe.path, args, {
|
|
||||||
\t\t\tstdio: "pipe",
|
|
||||||
\t\t\twindowsHide: true,
|
|
||||||
\t\t\tenv: {
|
|
||||||
\t\t\t\t...process.env,
|
|
||||||
\t\t\t\tHOME: os.homedir()
|
|
||||||
\t\t\t}
|
|
||||||
\t\t});`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'qmd runner',
|
|
||||||
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^qmd-manager-.*\.js$/),
|
|
||||||
search: `\t\t\tconst child = spawn(resolveWindowsCommandShim(this.qmd.command), args, {
|
|
||||||
\t\t\t\tenv: this.env,
|
|
||||||
\t\t\t\tcwd: this.workspaceDir
|
|
||||||
\t\t\t});`,
|
|
||||||
replace: `\t\t\tconst child = spawn(resolveWindowsCommandShim(this.qmd.command), args, {
|
|
||||||
\t\t\t\tenv: this.env,
|
|
||||||
\t\t\t\tcwd: this.workspaceDir,
|
|
||||||
\t\t\t\twindowsHide: true
|
|
||||||
\t\t\t});`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'mcporter runner',
|
|
||||||
target: () => findFirstFileByName(path.join(outputDir, 'dist', 'plugin-sdk'), /^qmd-manager-.*\.js$/),
|
|
||||||
search: `\t\t\tconst child = spawn(resolveWindowsCommandShim("mcporter"), args, {
|
|
||||||
\t\t\t\tenv: this.env,
|
|
||||||
\t\t\t\tcwd: this.workspaceDir
|
|
||||||
\t\t\t});`,
|
|
||||||
replace: `\t\t\tconst child = spawn(resolveWindowsCommandShim("mcporter"), args, {
|
|
||||||
\t\t\t\tenv: this.env,
|
|
||||||
\t\t\t\tcwd: this.workspaceDir,
|
|
||||||
\t\t\t\twindowsHide: true
|
|
||||||
\t\t\t});`,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"qqbot": {
|
"qqbot": {
|
||||||
"description": "Connect QQ Bot via @tencent-connect/openclaw-qqbot plugin",
|
"description": "Connect QQ Bot channel (built-in since OpenClaw 3.31)",
|
||||||
"docsUrl": "https://icnnp7d0dymg.feishu.cn/wiki/KPIJwlyiGiupMrkiS9ice39Zn2c",
|
"docsUrl": "https://icnnp7d0dymg.feishu.cn/wiki/KPIJwlyiGiupMrkiS9ice39Zn2c",
|
||||||
"fields": {
|
"fields": {
|
||||||
"appId": {
|
"appId": {
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
"docsUrl": "https://docs.openclaw.ai/channels/mattermost"
|
"docsUrl": "https://docs.openclaw.ai/channels/mattermost"
|
||||||
},
|
},
|
||||||
"qqbot": {
|
"qqbot": {
|
||||||
"description": "@tencent-connect/openclaw-qqbot プラグイン経由で QQ ボットに接続します",
|
"description": "QQ ボットチャンネルに接続します(OpenClaw 3.31 より内蔵)",
|
||||||
"fields": {
|
"fields": {
|
||||||
"appId": {
|
"appId": {
|
||||||
"label": "App ID",
|
"label": "App ID",
|
||||||
|
|||||||
@@ -369,7 +369,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"qqbot": {
|
"qqbot": {
|
||||||
"description": "通过 @tencent-connect/openclaw-qqbot 插件连接 QQ 机器人",
|
"description": "连接 QQ 机器人频道(OpenClaw 3.31 起内置)",
|
||||||
"docsUrl": "https://icnnp7d0dymg.feishu.cn/wiki/KPIJwlyiGiupMrkiS9ice39Zn2c",
|
"docsUrl": "https://icnnp7d0dymg.feishu.cn/wiki/KPIJwlyiGiupMrkiS9ice39Zn2c",
|
||||||
"fields": {
|
"fields": {
|
||||||
"appId": {
|
"appId": {
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ export const CHANNEL_META: Record<ChannelType, ChannelMeta> = {
|
|||||||
'channels:meta.qqbot.instructions.1',
|
'channels:meta.qqbot.instructions.1',
|
||||||
'channels:meta.qqbot.instructions.2',
|
'channels:meta.qqbot.instructions.2',
|
||||||
],
|
],
|
||||||
isPlugin: true,
|
|
||||||
},
|
},
|
||||||
dingtalk: {
|
dingtalk: {
|
||||||
id: 'dingtalk',
|
id: 'dingtalk',
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ describe('WeCom plugin configuration', () => {
|
|||||||
expect(channels.whatsapp.enabled).toBe(true);
|
expect(channels.whatsapp.enabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps configured built-in channels in plugins.allow when a plugin-backed channel is enabled', async () => {
|
it('saves qqbot as a built-in channel without plugin registration (OpenClaw 3.31+)', async () => {
|
||||||
const { saveChannelConfig } = await import('@electron/utils/channel-config');
|
const { saveChannelConfig } = await import('@electron/utils/channel-config');
|
||||||
|
|
||||||
await saveChannelConfig('discord', { token: 'discord-token' }, 'default');
|
await saveChannelConfig('discord', { token: 'discord-token' }, 'default');
|
||||||
@@ -202,9 +202,17 @@ describe('WeCom plugin configuration', () => {
|
|||||||
await saveChannelConfig('qqbot', { appId: 'qq-app', token: 'qq-token', appSecret: 'qq-secret' }, 'default');
|
await saveChannelConfig('qqbot', { appId: 'qq-app', token: 'qq-token', appSecret: 'qq-secret' }, 'default');
|
||||||
|
|
||||||
const config = await readOpenClawJson();
|
const config = await readOpenClawJson();
|
||||||
const plugins = config.plugins as { allow: string[] };
|
const channels = config.channels as Record<string, { accounts?: Record<string, unknown> }>;
|
||||||
|
|
||||||
expect(plugins.allow).toEqual(expect.arrayContaining(['openclaw-qqbot', 'discord', 'whatsapp']));
|
// QQBot config should be saved under channels.qqbot
|
||||||
|
expect(channels.qqbot.accounts?.default).toBeDefined();
|
||||||
|
|
||||||
|
// QQBot should NOT appear in plugins.entries (built-in channel)
|
||||||
|
const plugins = config.plugins as { entries?: Record<string, unknown> } | undefined;
|
||||||
|
if (plugins?.entries) {
|
||||||
|
expect(plugins.entries['openclaw-qqbot']).toBeUndefined();
|
||||||
|
expect(plugins.entries['qqbot']).toBeUndefined();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ vi.mock('@electron/utils/agent-config', () => ({
|
|||||||
vi.mock('@electron/utils/plugin-install', () => ({
|
vi.mock('@electron/utils/plugin-install', () => ({
|
||||||
ensureDingTalkPluginInstalled: vi.fn(),
|
ensureDingTalkPluginInstalled: vi.fn(),
|
||||||
ensureFeishuPluginInstalled: vi.fn(),
|
ensureFeishuPluginInstalled: vi.fn(),
|
||||||
ensureQQBotPluginInstalled: vi.fn(),
|
|
||||||
ensureWeChatPluginInstalled: vi.fn(),
|
ensureWeChatPluginInstalled: vi.fn(),
|
||||||
ensureWeComPluginInstalled: vi.fn(),
|
ensureWeComPluginInstalled: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user