feat(electron): auto-upgrade bundled OpenClaw plugins at app startup (#521)
This commit is contained in:
committed by
GitHub
Unverified
parent
02d38d15db
commit
9ec23174c0
@@ -1,8 +1,4 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from 'http';
|
import type { IncomingMessage, ServerResponse } from 'http';
|
||||||
import { app } from 'electron';
|
|
||||||
import { existsSync, cpSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
|
||||||
import { homedir } from 'node:os';
|
|
||||||
import { join } from 'node:path';
|
|
||||||
import {
|
import {
|
||||||
deleteChannelConfig,
|
deleteChannelConfig,
|
||||||
getChannelFormValues,
|
getChannelFormValues,
|
||||||
@@ -12,6 +8,12 @@ import {
|
|||||||
validateChannelConfig,
|
validateChannelConfig,
|
||||||
validateChannelCredentials,
|
validateChannelCredentials,
|
||||||
} from '../../utils/channel-config';
|
} from '../../utils/channel-config';
|
||||||
|
import {
|
||||||
|
ensureDingTalkPluginInstalled,
|
||||||
|
ensureFeishuPluginInstalled,
|
||||||
|
ensureQQBotPluginInstalled,
|
||||||
|
ensureWeComPluginInstalled,
|
||||||
|
} from '../../utils/plugin-install';
|
||||||
import { assignChannelToAgent, clearAllBindingsForChannel } from '../../utils/agent-config';
|
import { assignChannelToAgent, clearAllBindingsForChannel } from '../../utils/agent-config';
|
||||||
import { whatsAppLoginManager } from '../../utils/whatsapp-login';
|
import { whatsAppLoginManager } from '../../utils/whatsapp-login';
|
||||||
import type { HostApiContext } from '../context';
|
import type { HostApiContext } from '../context';
|
||||||
@@ -46,100 +48,6 @@ function scheduleGatewayChannelSaveRefresh(
|
|||||||
void reason;
|
void reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Generic plugin installer with version-aware upgrades ─────────
|
|
||||||
|
|
||||||
function readPluginVersion(pkgJsonPath: string): string | null {
|
|
||||||
try {
|
|
||||||
const raw = readFileSync(pkgJsonPath, 'utf-8');
|
|
||||||
const parsed = JSON.parse(raw) as { version?: string };
|
|
||||||
return parsed.version ?? null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensurePluginInstalled(
|
|
||||||
pluginDirName: string,
|
|
||||||
candidateSources: string[],
|
|
||||||
pluginLabel: string,
|
|
||||||
): { installed: boolean; warning?: string } {
|
|
||||||
const targetDir = join(homedir(), '.openclaw', 'extensions', pluginDirName);
|
|
||||||
const targetManifest = join(targetDir, 'openclaw.plugin.json');
|
|
||||||
const targetPkgJson = join(targetDir, 'package.json');
|
|
||||||
|
|
||||||
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
|
|
||||||
|
|
||||||
// If already installed, check whether an upgrade is available
|
|
||||||
if (existsSync(targetManifest)) {
|
|
||||||
if (!sourceDir) return { installed: true }; // no bundled source to compare, keep existing
|
|
||||||
const installedVersion = readPluginVersion(targetPkgJson);
|
|
||||||
const sourceVersion = readPluginVersion(join(sourceDir, 'package.json'));
|
|
||||||
if (!sourceVersion || !installedVersion || sourceVersion === installedVersion) {
|
|
||||||
return { installed: true }; // same version or unable to compare
|
|
||||||
}
|
|
||||||
// Version differs — fall through to overwrite install
|
|
||||||
console.log(
|
|
||||||
`[plugin] Upgrading ${pluginLabel} plugin: ${installedVersion} → ${sourceVersion}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fresh install or upgrade
|
|
||||||
if (!sourceDir) {
|
|
||||||
return {
|
|
||||||
installed: false,
|
|
||||||
warning: `Bundled ${pluginLabel} 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(join(targetDir, 'openclaw.plugin.json'))) {
|
|
||||||
return { installed: false, warning: `Failed to install ${pluginLabel} plugin mirror (manifest missing).` };
|
|
||||||
}
|
|
||||||
return { installed: true };
|
|
||||||
} catch {
|
|
||||||
return { installed: false, warning: `Failed to install bundled ${pluginLabel} plugin mirror` };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Per-channel plugin helpers (thin wrappers around ensurePluginInstalled) ──
|
|
||||||
|
|
||||||
function buildCandidateSources(pluginDirName: string): string[] {
|
|
||||||
return app.isPackaged
|
|
||||||
? [
|
|
||||||
join(process.resourcesPath, 'openclaw-plugins', pluginDirName),
|
|
||||||
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', pluginDirName),
|
|
||||||
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', pluginDirName),
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
join(app.getAppPath(), 'build', 'openclaw-plugins', pluginDirName),
|
|
||||||
join(process.cwd(), 'build', 'openclaw-plugins', pluginDirName),
|
|
||||||
join(__dirname, '../../../build/openclaw-plugins', pluginDirName),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureDingTalkPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('dingtalk', buildCandidateSources('dingtalk'), 'DingTalk');
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureWeComPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('wecom', buildCandidateSources('wecom'), 'WeCom');
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureFeishuPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled(
|
|
||||||
'feishu-openclaw-plugin',
|
|
||||||
buildCandidateSources('feishu-openclaw-plugin'),
|
|
||||||
'Feishu',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureQQBotPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('qqbot', buildCandidateSources('qqbot'), 'QQ Bot');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toComparableConfig(input: Record<string, unknown>): Record<string, string> {
|
function toComparableConfig(input: Record<string, unknown>): Record<string, string> {
|
||||||
const next: Record<string, string> = {};
|
const next: Record<string, string> = {};
|
||||||
for (const [key, value] of Object.entries(input)) {
|
for (const [key, value] of Object.entries(input)) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
} from './main-window-focus';
|
} from './main-window-focus';
|
||||||
import { getSetting } from '../utils/store';
|
import { getSetting } from '../utils/store';
|
||||||
import { ensureBuiltinSkillsInstalled, ensurePreinstalledSkillsInstalled } from '../utils/skill-config';
|
import { ensureBuiltinSkillsInstalled, ensurePreinstalledSkillsInstalled } from '../utils/skill-config';
|
||||||
|
import { ensureAllBundledPluginsInstalled } from '../utils/plugin-install';
|
||||||
import { startHostApiServer } from '../api/server';
|
import { startHostApiServer } from '../api/server';
|
||||||
import { HostEventBus } from '../api/event-bus';
|
import { HostEventBus } from '../api/event-bus';
|
||||||
import { deviceOAuthManager } from '../utils/device-oauth';
|
import { deviceOAuthManager } from '../utils/device-oauth';
|
||||||
@@ -294,6 +295,12 @@ async function initialize(): Promise<void> {
|
|||||||
logger.warn('Failed to install preinstalled skills:', error);
|
logger.warn('Failed to install preinstalled skills:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pre-deploy/upgrade bundled OpenClaw plugins (dingtalk, wecom, qqbot, feishu)
|
||||||
|
// to ~/.openclaw/extensions/ so they are always up-to-date after an app update.
|
||||||
|
void ensureAllBundledPluginsInstalled().catch((error) => {
|
||||||
|
logger.warn('Failed to install/upgrade bundled plugins:', error);
|
||||||
|
});
|
||||||
|
|
||||||
// Bridge gateway and host-side events before any auto-start logic runs, so
|
// Bridge gateway and host-side events before any auto-start logic runs, so
|
||||||
// renderer subscribers observe the full startup lifecycle.
|
// renderer subscribers observe the full startup lifecycle.
|
||||||
gatewayManager.on('status', (status: { state: string }) => {
|
gatewayManager.on('status', (status: { state: string }) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Registers all IPC handlers for main-renderer communication
|
* Registers all IPC handlers for main-renderer communication
|
||||||
*/
|
*/
|
||||||
import { ipcMain, BrowserWindow, shell, dialog, app, nativeImage } from 'electron';
|
import { ipcMain, BrowserWindow, shell, dialog, app, nativeImage } from 'electron';
|
||||||
import { existsSync, cpSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
import { join, extname, basename } from 'node:path';
|
import { join, extname, basename } from 'node:path';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
@@ -32,6 +32,12 @@ import {
|
|||||||
validateChannelCredentials,
|
validateChannelCredentials,
|
||||||
} from '../utils/channel-config';
|
} from '../utils/channel-config';
|
||||||
import { checkUvInstalled, installUv, setupManagedPython } from '../utils/uv-setup';
|
import { checkUvInstalled, installUv, setupManagedPython } from '../utils/uv-setup';
|
||||||
|
import {
|
||||||
|
ensureDingTalkPluginInstalled,
|
||||||
|
ensureFeishuPluginInstalled,
|
||||||
|
ensureQQBotPluginInstalled,
|
||||||
|
ensureWeComPluginInstalled,
|
||||||
|
} from '../utils/plugin-install';
|
||||||
import { updateSkillConfig, getSkillConfig, getAllSkillConfigs } from '../utils/skill-config';
|
import { updateSkillConfig, getSkillConfig, getAllSkillConfigs } from '../utils/skill-config';
|
||||||
import { whatsAppLoginManager } from '../utils/whatsapp-login';
|
import { whatsAppLoginManager } from '../utils/whatsapp-login';
|
||||||
import { getProviderConfig } from '../utils/provider-registry';
|
import { getProviderConfig } from '../utils/provider-registry';
|
||||||
@@ -1382,102 +1388,6 @@ function registerOpenClawHandlers(gatewayManager: GatewayManager): void {
|
|||||||
gatewayManager.debouncedReload();
|
gatewayManager.debouncedReload();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Generic plugin installer with version-aware upgrades ─────────
|
|
||||||
|
|
||||||
function readPluginVersion(pkgJsonPath: string): string | null {
|
|
||||||
try {
|
|
||||||
const raw = readFileSync(pkgJsonPath, 'utf-8');
|
|
||||||
const parsed = JSON.parse(raw) as { version?: string };
|
|
||||||
return parsed.version ?? null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensurePluginInstalled(
|
|
||||||
pluginDirName: string,
|
|
||||||
candidateSources: string[],
|
|
||||||
pluginLabel: string,
|
|
||||||
): { installed: boolean; warning?: string } {
|
|
||||||
const targetDir = join(homedir(), '.openclaw', 'extensions', pluginDirName);
|
|
||||||
const targetManifest = join(targetDir, 'openclaw.plugin.json');
|
|
||||||
const targetPkgJson = join(targetDir, 'package.json');
|
|
||||||
|
|
||||||
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
|
|
||||||
|
|
||||||
// If already installed, check whether an upgrade is available
|
|
||||||
if (existsSync(targetManifest)) {
|
|
||||||
if (!sourceDir) return { installed: true };
|
|
||||||
const installedVersion = readPluginVersion(targetPkgJson);
|
|
||||||
const sourceVersion = readPluginVersion(join(sourceDir, 'package.json'));
|
|
||||||
if (!sourceVersion || !installedVersion || sourceVersion === installedVersion) {
|
|
||||||
return { installed: true };
|
|
||||||
}
|
|
||||||
logger.info(`[plugin] Upgrading ${pluginLabel} plugin: ${installedVersion} → ${sourceVersion}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sourceDir) {
|
|
||||||
logger.warn(`Bundled ${pluginLabel} plugin mirror not found in candidate paths`, { candidateSources });
|
|
||||||
return {
|
|
||||||
installed: false,
|
|
||||||
warning: `Bundled ${pluginLabel} 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(join(targetDir, 'openclaw.plugin.json'))) {
|
|
||||||
return { installed: false, warning: `Failed to install ${pluginLabel} plugin mirror (manifest missing).` };
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Installed ${pluginLabel} plugin from bundled mirror: ${sourceDir}`);
|
|
||||||
return { installed: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(`Failed to install ${pluginLabel} plugin from bundled mirror:`, error);
|
|
||||||
return {
|
|
||||||
installed: false,
|
|
||||||
warning: `Failed to install bundled ${pluginLabel} plugin mirror`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCandidateSources(pluginDirName: string): string[] {
|
|
||||||
return app.isPackaged
|
|
||||||
? [
|
|
||||||
join(process.resourcesPath, 'openclaw-plugins', pluginDirName),
|
|
||||||
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', pluginDirName),
|
|
||||||
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', pluginDirName),
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
join(app.getAppPath(), 'build', 'openclaw-plugins', pluginDirName),
|
|
||||||
join(process.cwd(), 'build', 'openclaw-plugins', pluginDirName),
|
|
||||||
join(__dirname, '../../build/openclaw-plugins', pluginDirName),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureDingTalkPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('dingtalk', buildCandidateSources('dingtalk'), 'DingTalk');
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureWeComPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('wecom', buildCandidateSources('wecom'), 'WeCom');
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureFeishuPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled(
|
|
||||||
'feishu-openclaw-plugin',
|
|
||||||
buildCandidateSources('feishu-openclaw-plugin'),
|
|
||||||
'Feishu',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureQQBotPluginInstalled(): { installed: boolean; warning?: string } {
|
|
||||||
return ensurePluginInstalled('qqbot', buildCandidateSources('qqbot'), 'QQ Bot');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get OpenClaw package status
|
// Get OpenClaw package status
|
||||||
ipcMain.handle('openclaw:status', () => {
|
ipcMain.handle('openclaw:status', () => {
|
||||||
const status = getOpenClawStatus();
|
const status = getOpenClawStatus();
|
||||||
|
|||||||
141
electron/utils/plugin-install.ts
Normal file
141
electron/utils/plugin-install.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* Shared OpenClaw Plugin Install Utilities
|
||||||
|
*
|
||||||
|
* Provides version-aware install/upgrade logic for bundled OpenClaw plugins
|
||||||
|
* (DingTalk, WeCom, QQBot, Feishu). Used both at app startup (to auto-upgrade
|
||||||
|
* stale plugins) and when a user configures a channel.
|
||||||
|
*/
|
||||||
|
import { app } from 'electron';
|
||||||
|
import { existsSync, cpSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
||||||
|
import { homedir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { logger } from './logger';
|
||||||
|
|
||||||
|
// ── Version helper ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function readPluginVersion(pkgJsonPath: string): string | null {
|
||||||
|
try {
|
||||||
|
const raw = readFileSync(pkgJsonPath, 'utf-8');
|
||||||
|
const parsed = JSON.parse(raw) as { version?: string };
|
||||||
|
return parsed.version ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Core install / upgrade logic ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function ensurePluginInstalled(
|
||||||
|
pluginDirName: string,
|
||||||
|
candidateSources: string[],
|
||||||
|
pluginLabel: string,
|
||||||
|
): { installed: boolean; warning?: string } {
|
||||||
|
const targetDir = join(homedir(), '.openclaw', 'extensions', pluginDirName);
|
||||||
|
const targetManifest = join(targetDir, 'openclaw.plugin.json');
|
||||||
|
const targetPkgJson = join(targetDir, 'package.json');
|
||||||
|
|
||||||
|
const sourceDir = candidateSources.find((dir) => existsSync(join(dir, 'openclaw.plugin.json')));
|
||||||
|
|
||||||
|
// If already installed, check whether an upgrade is available
|
||||||
|
if (existsSync(targetManifest)) {
|
||||||
|
if (!sourceDir) return { installed: true }; // no bundled source to compare, keep existing
|
||||||
|
const installedVersion = readPluginVersion(targetPkgJson);
|
||||||
|
const sourceVersion = readPluginVersion(join(sourceDir, 'package.json'));
|
||||||
|
if (!sourceVersion || !installedVersion || sourceVersion === installedVersion) {
|
||||||
|
return { installed: true }; // same version or unable to compare
|
||||||
|
}
|
||||||
|
// Version differs — fall through to overwrite install
|
||||||
|
logger.info(
|
||||||
|
`[plugin] Upgrading ${pluginLabel} plugin: ${installedVersion} → ${sourceVersion}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh install or upgrade
|
||||||
|
if (!sourceDir) {
|
||||||
|
return {
|
||||||
|
installed: false,
|
||||||
|
warning: `Bundled ${pluginLabel} 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(join(targetDir, 'openclaw.plugin.json'))) {
|
||||||
|
return { installed: false, warning: `Failed to install ${pluginLabel} plugin mirror (manifest missing).` };
|
||||||
|
}
|
||||||
|
logger.info(`Installed ${pluginLabel} plugin from bundled mirror: ${sourceDir}`);
|
||||||
|
return { installed: true };
|
||||||
|
} catch {
|
||||||
|
return { installed: false, warning: `Failed to install bundled ${pluginLabel} plugin mirror` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Candidate source path builder ────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function buildCandidateSources(pluginDirName: string): string[] {
|
||||||
|
return app.isPackaged
|
||||||
|
? [
|
||||||
|
join(process.resourcesPath, 'openclaw-plugins', pluginDirName),
|
||||||
|
join(process.resourcesPath, 'app.asar.unpacked', 'build', 'openclaw-plugins', pluginDirName),
|
||||||
|
join(process.resourcesPath, 'app.asar.unpacked', 'openclaw-plugins', pluginDirName),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
join(app.getAppPath(), 'build', 'openclaw-plugins', pluginDirName),
|
||||||
|
join(process.cwd(), 'build', 'openclaw-plugins', pluginDirName),
|
||||||
|
join(__dirname, '../../build/openclaw-plugins', pluginDirName),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Per-channel plugin helpers ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function ensureDingTalkPluginInstalled(): { installed: boolean; warning?: string } {
|
||||||
|
return ensurePluginInstalled('dingtalk', buildCandidateSources('dingtalk'), 'DingTalk');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureWeComPluginInstalled(): { installed: boolean; warning?: string } {
|
||||||
|
return ensurePluginInstalled('wecom', buildCandidateSources('wecom'), 'WeCom');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureFeishuPluginInstalled(): { installed: boolean; warning?: string } {
|
||||||
|
return ensurePluginInstalled(
|
||||||
|
'feishu-openclaw-plugin',
|
||||||
|
buildCandidateSources('feishu-openclaw-plugin'),
|
||||||
|
'Feishu',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureQQBotPluginInstalled(): { installed: boolean; warning?: string } {
|
||||||
|
return ensurePluginInstalled('qqbot', buildCandidateSources('qqbot'), 'QQ Bot');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bulk startup installer ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All bundled plugins, in the same order as after-pack.cjs BUNDLED_PLUGINS.
|
||||||
|
*/
|
||||||
|
const ALL_BUNDLED_PLUGINS = [
|
||||||
|
{ fn: ensureDingTalkPluginInstalled, label: 'DingTalk' },
|
||||||
|
{ fn: ensureWeComPluginInstalled, label: 'WeCom' },
|
||||||
|
{ fn: ensureQQBotPluginInstalled, label: 'QQ Bot' },
|
||||||
|
{ fn: ensureFeishuPluginInstalled, label: 'Feishu' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure all bundled OpenClaw plugins are installed/upgraded in
|
||||||
|
* `~/.openclaw/extensions/`. Designed to be called once at app startup
|
||||||
|
* as a fire-and-forget task — errors are logged but never thrown.
|
||||||
|
*/
|
||||||
|
export async function ensureAllBundledPluginsInstalled(): Promise<void> {
|
||||||
|
for (const { fn, label } of ALL_BUNDLED_PLUGINS) {
|
||||||
|
try {
|
||||||
|
const result = fn();
|
||||||
|
if (result.warning) {
|
||||||
|
logger.warn(`[plugin] ${label}: ${result.warning}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`[plugin] Failed to install/upgrade ${label} plugin:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@larksuite/openclaw-lark": "2026.3.12",
|
"@larksuite/openclaw-lark": "2026.3.15",
|
||||||
"@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",
|
||||||
|
|||||||
106
pnpm-lock.yaml
generated
106
pnpm-lock.yaml
generated
@@ -37,8 +37,8 @@ importers:
|
|||||||
specifier: ^10.0.1
|
specifier: ^10.0.1
|
||||||
version: 10.0.1(eslint@10.0.0(jiti@1.21.7))
|
version: 10.0.1(eslint@10.0.0(jiti@1.21.7))
|
||||||
'@larksuite/openclaw-lark':
|
'@larksuite/openclaw-lark':
|
||||||
specifier: 2026.3.12
|
specifier: 2026.3.15
|
||||||
version: 2026.3.12
|
version: 2026.3.15
|
||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.1.15
|
specifier: ^1.1.15
|
||||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
@@ -296,10 +296,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ag7Qx78m1K3Dv7xlFgeHS4jBdopGZUISgVBMUy7Cj4fIgVH9EBmsc5K4hWozL8BJQctWke8Wsl96O7Gd+HCGhg==}
|
resolution: {integrity: sha512-ag7Qx78m1K3Dv7xlFgeHS4jBdopGZUISgVBMUy7Cj4fIgVH9EBmsc5K4hWozL8BJQctWke8Wsl96O7Gd+HCGhg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1006.0':
|
|
||||||
resolution: {integrity: sha512-CWUrrlWaFDYZIK2rNIa9FUVn3wC3lZszz0r6bq5yRiRj1P+dSd+ZpGdlRYDAi6Nq9dsainEXdpadiuUwzL6YZQ==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1009.0':
|
'@aws-sdk/client-bedrock@3.1009.0':
|
||||||
resolution: {integrity: sha512-KzLNqSg1T59sSlQvEA4EL3oDIAMidM54AB1b+UGouPFuUrrwGp2uUlZUYzIIlCvqpf7wEDh8wypqXISRItkgdg==}
|
resolution: {integrity: sha512-KzLNqSg1T59sSlQvEA4EL3oDIAMidM54AB1b+UGouPFuUrrwGp2uUlZUYzIIlCvqpf7wEDh8wypqXISRItkgdg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -444,10 +440,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw==}
|
resolution: {integrity: sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1006.0':
|
|
||||||
resolution: {integrity: sha512-eCBaQI1w5PcliOdh8Y0YONOim2zNSTEK4E7gXYC4vIqiT/lzVODIFxmpc8oOBLPSANzcr9daIPPtjQ2C75dLFg==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1009.0':
|
'@aws-sdk/token-providers@3.1009.0':
|
||||||
resolution: {integrity: sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA==}
|
resolution: {integrity: sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -1229,8 +1221,8 @@ packages:
|
|||||||
'@kwsites/promise-deferred@1.1.1':
|
'@kwsites/promise-deferred@1.1.1':
|
||||||
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
|
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
|
||||||
|
|
||||||
'@larksuite/openclaw-lark@2026.3.12':
|
'@larksuite/openclaw-lark@2026.3.15':
|
||||||
resolution: {integrity: sha512-MNcDrerQrO42I09w+M8q6dwnWHMKxOnXSCLG4qNwcekjGeDmA53lIuWJtGMpjTzvDjYkoWnN+8Zg78+FRCSV9w==}
|
resolution: {integrity: sha512-YGk63CgYG+YDUO2+TxdSPJZA6km52OpqAqf26vZHMUqawEKhiT4bcfD5JHh6VGazjUwRk5Bze1UyVKncc0csBA==}
|
||||||
|
|
||||||
'@larksuiteoapi/node-sdk@1.59.0':
|
'@larksuiteoapi/node-sdk@1.59.0':
|
||||||
resolution: {integrity: sha512-sBpkruTvZDOxnVtoTbepWKRX0j1Y1ZElQYu0x7+v088sI9pcpbVp6ZzCGn62dhrKPatzNyCJyzYCPXPYQWccrA==}
|
resolution: {integrity: sha512-sBpkruTvZDOxnVtoTbepWKRX0j1Y1ZElQYu0x7+v088sI9pcpbVp6ZzCGn62dhrKPatzNyCJyzYCPXPYQWccrA==}
|
||||||
@@ -2513,10 +2505,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A==}
|
resolution: {integrity: sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A==}
|
||||||
engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
|
engines: {node: '>= 12.13.0', npm: '>= 6.12.0'}
|
||||||
|
|
||||||
'@slack/web-api@7.14.1':
|
|
||||||
resolution: {integrity: sha512-RoygyteJeFswxDPJjUMESn9dldWVMD2xUcHHd9DenVavSfVC6FeVnSdDerOO7m8LLvw4Q132nQM4hX8JiF7dng==}
|
|
||||||
engines: {node: '>= 18', npm: '>= 8.6.0'}
|
|
||||||
|
|
||||||
'@slack/web-api@7.15.0':
|
'@slack/web-api@7.15.0':
|
||||||
resolution: {integrity: sha512-va7zYIt3QHG1x9M/jqXXRPFMoOVlVSSRHC5YH+DzKYsrz5xUKOA3lR4THsu/Zxha9N1jOndbKFKLtr0WOPW1Vw==}
|
resolution: {integrity: sha512-va7zYIt3QHG1x9M/jqXXRPFMoOVlVSSRHC5YH+DzKYsrz5xUKOA3lR4THsu/Zxha9N1jOndbKFKLtr0WOPW1Vw==}
|
||||||
engines: {node: '>= 18', npm: '>= 8.6.0'}
|
engines: {node: '>= 18', npm: '>= 8.6.0'}
|
||||||
@@ -7211,51 +7199,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1006.0':
|
|
||||||
dependencies:
|
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
|
||||||
'@aws-crypto/sha256-js': 5.2.0
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/credential-provider-node': 3.972.19
|
|
||||||
'@aws-sdk/middleware-host-header': 3.972.7
|
|
||||||
'@aws-sdk/middleware-logger': 3.972.7
|
|
||||||
'@aws-sdk/middleware-recursion-detection': 3.972.7
|
|
||||||
'@aws-sdk/middleware-user-agent': 3.972.20
|
|
||||||
'@aws-sdk/region-config-resolver': 3.972.7
|
|
||||||
'@aws-sdk/token-providers': 3.1006.0
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@aws-sdk/util-endpoints': 3.996.4
|
|
||||||
'@aws-sdk/util-user-agent-browser': 3.972.7
|
|
||||||
'@aws-sdk/util-user-agent-node': 3.973.5
|
|
||||||
'@smithy/config-resolver': 4.4.10
|
|
||||||
'@smithy/core': 3.23.9
|
|
||||||
'@smithy/fetch-http-handler': 5.3.13
|
|
||||||
'@smithy/hash-node': 4.2.11
|
|
||||||
'@smithy/invalid-dependency': 4.2.11
|
|
||||||
'@smithy/middleware-content-length': 4.2.11
|
|
||||||
'@smithy/middleware-endpoint': 4.4.23
|
|
||||||
'@smithy/middleware-retry': 4.4.40
|
|
||||||
'@smithy/middleware-serde': 4.2.12
|
|
||||||
'@smithy/middleware-stack': 4.2.11
|
|
||||||
'@smithy/node-config-provider': 4.3.11
|
|
||||||
'@smithy/node-http-handler': 4.4.14
|
|
||||||
'@smithy/protocol-http': 5.3.11
|
|
||||||
'@smithy/smithy-client': 4.12.3
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
'@smithy/url-parser': 4.2.11
|
|
||||||
'@smithy/util-base64': 4.3.2
|
|
||||||
'@smithy/util-body-length-browser': 4.2.2
|
|
||||||
'@smithy/util-body-length-node': 4.2.3
|
|
||||||
'@smithy/util-defaults-mode-browser': 4.3.39
|
|
||||||
'@smithy/util-defaults-mode-node': 4.2.42
|
|
||||||
'@smithy/util-endpoints': 3.3.2
|
|
||||||
'@smithy/util-middleware': 4.2.11
|
|
||||||
'@smithy/util-retry': 4.2.11
|
|
||||||
'@smithy/util-utf8': 4.2.2
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1009.0':
|
'@aws-sdk/client-bedrock@3.1009.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
'@aws-crypto/sha256-browser': 5.2.0
|
||||||
@@ -7791,18 +7734,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1006.0':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1009.0':
|
'@aws-sdk/token-providers@3.1009.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.973.20
|
'@aws-sdk/core': 3.973.20
|
||||||
@@ -8671,7 +8602,7 @@ snapshots:
|
|||||||
'@kwsites/promise-deferred@1.1.1':
|
'@kwsites/promise-deferred@1.1.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@larksuite/openclaw-lark@2026.3.12':
|
'@larksuite/openclaw-lark@2026.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@larksuiteoapi/node-sdk': 1.59.0
|
'@larksuiteoapi/node-sdk': 1.59.0
|
||||||
'@sinclair/typebox': 0.34.48
|
'@sinclair/typebox': 0.34.48
|
||||||
@@ -8874,7 +8805,7 @@ snapshots:
|
|||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
diff: 8.0.3
|
diff: 8.0.3
|
||||||
file-type: 21.3.0
|
file-type: 21.3.2
|
||||||
glob: 11.1.0
|
glob: 11.1.0
|
||||||
marked: 15.0.12
|
marked: 15.0.12
|
||||||
minimatch: 10.2.4
|
minimatch: 10.2.4
|
||||||
@@ -9961,23 +9892,6 @@ snapshots:
|
|||||||
|
|
||||||
'@slack/types@2.20.1': {}
|
'@slack/types@2.20.1': {}
|
||||||
|
|
||||||
'@slack/web-api@7.14.1':
|
|
||||||
dependencies:
|
|
||||||
'@slack/logger': 4.0.0
|
|
||||||
'@slack/types': 2.20.0
|
|
||||||
'@types/node': 25.3.0
|
|
||||||
'@types/retry': 0.12.0
|
|
||||||
axios: 1.13.5(debug@4.4.3)
|
|
||||||
eventemitter3: 5.0.4
|
|
||||||
form-data: 4.0.5
|
|
||||||
is-electron: 2.2.2
|
|
||||||
is-stream: 2.0.1
|
|
||||||
p-queue: 6.6.2
|
|
||||||
p-retry: 4.6.2
|
|
||||||
retry: 0.13.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
|
|
||||||
'@slack/web-api@7.15.0':
|
'@slack/web-api@7.15.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@slack/logger': 4.0.1
|
'@slack/logger': 4.0.1
|
||||||
@@ -11467,7 +11381,7 @@ snapshots:
|
|||||||
clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(@types/express@5.0.6)(devtools-protocol@0.0.1596832)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3):
|
clawdbot@2026.1.24-3(@discordjs/opus@0.10.0(encoding@0.1.13))(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(@types/express@5.0.6)(devtools-protocol@0.0.1596832)(encoding@0.1.13)(opusscript@0.1.1)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
||||||
'@aws-sdk/client-bedrock': 3.1006.0
|
'@aws-sdk/client-bedrock': 3.1009.0
|
||||||
'@buape/carbon': 0.14.0(@discordjs/opus@0.10.0(encoding@0.1.13))(hono@4.11.4)(opusscript@0.1.1)
|
'@buape/carbon': 0.14.0(@discordjs/opus@0.10.0(encoding@0.1.13))(hono@4.11.4)(opusscript@0.1.1)
|
||||||
'@clack/prompts': 0.11.0
|
'@clack/prompts': 0.11.0
|
||||||
'@grammyjs/runner': 2.0.3(grammy@1.41.1(encoding@0.1.13))
|
'@grammyjs/runner': 2.0.3(grammy@1.41.1(encoding@0.1.13))
|
||||||
@@ -11482,7 +11396,7 @@ snapshots:
|
|||||||
'@mozilla/readability': 0.6.0
|
'@mozilla/readability': 0.6.0
|
||||||
'@sinclair/typebox': 0.34.47
|
'@sinclair/typebox': 0.34.47
|
||||||
'@slack/bolt': 4.6.0(@types/express@5.0.6)
|
'@slack/bolt': 4.6.0(@types/express@5.0.6)
|
||||||
'@slack/web-api': 7.14.1
|
'@slack/web-api': 7.15.0
|
||||||
'@whiskeysockets/baileys': 7.0.0-rc.9(sharp@0.34.5)
|
'@whiskeysockets/baileys': 7.0.0-rc.9(sharp@0.34.5)
|
||||||
ajv: 8.18.0
|
ajv: 8.18.0
|
||||||
body-parser: 2.2.2
|
body-parser: 2.2.2
|
||||||
@@ -11496,7 +11410,7 @@ snapshots:
|
|||||||
discord-api-types: 0.38.42
|
discord-api-types: 0.38.42
|
||||||
dotenv: 17.3.1
|
dotenv: 17.3.1
|
||||||
express: 5.2.1
|
express: 5.2.1
|
||||||
file-type: 21.3.0
|
file-type: 21.3.2
|
||||||
grammy: 1.41.1(encoding@0.1.13)
|
grammy: 1.41.1(encoding@0.1.13)
|
||||||
hono: 4.11.4
|
hono: 4.11.4
|
||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
@@ -11515,7 +11429,7 @@ snapshots:
|
|||||||
sqlite-vec: 0.1.7-alpha.2
|
sqlite-vec: 0.1.7-alpha.2
|
||||||
tar: 7.5.4
|
tar: 7.5.4
|
||||||
tslog: 4.10.2
|
tslog: 4.10.2
|
||||||
undici: 7.22.0
|
undici: 7.24.1
|
||||||
ws: 8.19.0
|
ws: 8.19.0
|
||||||
yaml: 2.8.2
|
yaml: 2.8.2
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
|
|||||||
Reference in New Issue
Block a user