feat: 渠道插件安装自动 pin 版本,兼容旧版 OpenClaw

- messaging.rs: install_channel_plugin/install_qqbot_plugin/run_channel_action 新增 version 参数
- 微信插件 npx 安装也支持版本 pin(@tencent-weixin/openclaw-weixin-cli@{version})
- channels.js: 安装前自动获取用户 OpenClaw 版本,pin 到基础版本号
- tauri-api.js/dev-api.js: 同步支持 version 参数传递
This commit is contained in:
晴天
2026-03-26 02:57:43 +08:00
parent ee09ebbc98
commit 540da00b91
4 changed files with 69 additions and 31 deletions

View File

@@ -1815,10 +1815,11 @@ const handlers = {
return { valid: true, warnings: ['该平台暂不支持在线校验'] }
},
install_qqbot_plugin() {
install_qqbot_plugin({ version } = {}) {
const bin = findOpenclawBin() || 'openclaw'
const spec = version ? `@tencent-connect/openclaw-qqbot@${version}` : '@tencent-connect/openclaw-qqbot@latest'
try {
execSync(`${bin} plugins install @tencent-connect/openclaw-qqbot@latest`, { timeout: 600000, cwd: homedir() })
execSync(`${bin} plugins install ${spec}`, { timeout: 600000, cwd: homedir() })
return '安装成功'
} catch (e) {
throw new Error('QQBot 插件安装失败: ' + (e.message || e))
@@ -1851,11 +1852,12 @@ const handlers = {
}
},
install_channel_plugin({ packageName, pluginId }) {
install_channel_plugin({ packageName, pluginId, version }) {
if (!packageName || !pluginId) throw new Error('packageName 和 pluginId 不能为空')
const bin = findOpenclawBin() || 'openclaw'
const spec = version ? `${packageName.trim()}@${version}` : packageName.trim()
try {
execSync(`${bin} plugins install ${packageName.trim()}`, { timeout: 120000, cwd: homedir() })
execSync(`${bin} plugins install ${spec}`, { timeout: 120000, cwd: homedir() })
return '安装成功'
} catch (e) {
throw new Error(`插件 ${pluginId} 安装失败: ` + (e.message || e))

View File

@@ -1104,6 +1104,7 @@ pub async fn run_channel_action(
app: tauri::AppHandle,
platform: String,
action: String,
version: Option<String>,
) -> Result<String, String> {
use std::io::{BufRead, BufReader};
use std::process::Stdio;
@@ -1118,10 +1119,17 @@ pub async fn run_channel_action(
// weixin install 走 npx 而非 openclaw CLI
if platform == "weixin" && action == "install" {
let _ = app.emit("channel-action-log", json!({
"platform": &platform, "action": &action, "kind": "info",
"message": "开始安装微信插件: npx -y @tencent-weixin/openclaw-weixin-cli@latest install",
}));
let weixin_spec = match &version {
Some(v) if !v.is_empty() => format!("@tencent-weixin/openclaw-weixin-cli@{}", v),
_ => "@tencent-weixin/openclaw-weixin-cli@latest".to_string(),
};
let _ = app.emit(
"channel-action-log",
json!({
"platform": &platform, "action": &action, "kind": "info",
"message": format!("开始安装微信插件: npx -y {} install", weixin_spec),
}),
);
let _ = app.emit(
"channel-action-progress",
json!({ "platform": &platform, "action": &action, "progress": 5 }),
@@ -1133,24 +1141,14 @@ pub async fn run_channel_action(
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
let mut c = std::process::Command::new("cmd");
c.args([
"/c",
"npx",
"-y",
"@tencent-weixin/openclaw-weixin-cli@latest",
"install",
]);
c.args(["/c", "npx", "-y", &weixin_spec, "install"]);
c.creation_flags(CREATE_NO_WINDOW);
c
};
#[cfg(not(target_os = "windows"))]
let mut cmd = {
let mut c = std::process::Command::new("npx");
c.args([
"-y",
"@tencent-weixin/openclaw-weixin-cli@latest",
"install",
]);
c.args(["-y", &weixin_spec, "install"]);
c
};
cmd.env("PATH", &path_env);
@@ -1522,7 +1520,7 @@ pub async fn diagnose_channel(
pub async fn repair_qqbot_channel_setup(app: tauri::AppHandle) -> Result<Value, String> {
let (installed, _loc) = qqbot_extension_installed();
if !installed {
install_qqbot_plugin(app.clone()).await?;
install_qqbot_plugin(app.clone(), None).await?;
return Ok(json!({
"ok": true,
"action": "installed",
@@ -2475,6 +2473,7 @@ pub async fn install_channel_plugin(
app: tauri::AppHandle,
package_name: String,
plugin_id: String,
version: Option<String>,
) -> Result<String, String> {
use std::io::{BufRead, BufReader};
use std::process::Stdio;
@@ -2485,6 +2484,11 @@ pub async fn install_channel_plugin(
if package_name.is_empty() || plugin_id.is_empty() {
return Err("package_name 和 plugin_id 不能为空".into());
}
// 拼接版本号package@version兼容用户 OpenClaw 版本的插件)
let install_spec = match &version {
Some(v) if !v.is_empty() => format!("{}@{}", package_name, v),
_ => package_name.to_string(),
};
let plugin_dir = generic_plugin_dir(plugin_id);
let plugin_backup = generic_plugin_backup_dir(plugin_id);
let config_path = super::openclaw_dir().join("openclaw.json");
@@ -2518,8 +2522,9 @@ pub async fn install_channel_plugin(
fs::copy(&config_path, &config_backup).map_err(|e| format!("备份配置失败: {e}"))?;
}
let _ = app.emit("plugin-log", format!("安装规格: {}", install_spec));
let spawn_result = crate::utils::openclaw_command()
.args(["plugins", "install", package_name])
.args(["plugins", "install", &install_spec])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn();
@@ -2637,11 +2642,19 @@ pub async fn install_channel_plugin(
}
#[tauri::command]
pub async fn install_qqbot_plugin(app: tauri::AppHandle) -> Result<String, String> {
pub async fn install_qqbot_plugin(
app: tauri::AppHandle,
version: Option<String>,
) -> Result<String, String> {
use std::io::{BufRead, BufReader};
use std::process::Stdio;
use tauri::Emitter;
let install_spec = match &version {
Some(v) if !v.is_empty() => format!("{}@{}", TENCENT_OPENCLAW_QQBOT_PACKAGE, v),
_ => TENCENT_OPENCLAW_QQBOT_PACKAGE.to_string(),
};
let plugin_dir = generic_plugin_dir(OPENCLAW_QQBOT_EXTENSION_FOLDER);
let plugin_backup = generic_plugin_backup_dir(OPENCLAW_QQBOT_EXTENSION_FOLDER);
let config_path = super::openclaw_dir().join("openclaw.json");
@@ -2678,8 +2691,9 @@ pub async fn install_qqbot_plugin(app: tauri::AppHandle) -> Result<String, Strin
fs::copy(&config_path, &config_backup).map_err(|e| format!("备份配置失败: {e}"))?;
}
let _ = app.emit("plugin-log", format!("安装规格: {}", install_spec));
let spawn_result = crate::utils::openclaw_command()
.args(["plugins", "install", TENCENT_OPENCLAW_QQBOT_PACKAGE])
.args(["plugins", "install", &install_spec])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn();

View File

@@ -225,9 +225,9 @@ export const api = {
},
listConfiguredPlatforms: () => cachedInvoke('list_configured_platforms', {}, 5000),
getChannelPluginStatus: (pluginId) => invoke('get_channel_plugin_status', { pluginId }),
installQqbotPlugin: () => invoke('install_qqbot_plugin'),
installChannelPlugin: (packageName, pluginId) => invoke('install_channel_plugin', { packageName, pluginId }),
runChannelAction: (platform, action) => invoke('run_channel_action', { platform, action }),
installQqbotPlugin: (version = null) => invoke('install_qqbot_plugin', { version }),
installChannelPlugin: (packageName, pluginId, version = null) => invoke('install_channel_plugin', { packageName, pluginId, version }),
runChannelAction: (platform, action, version = null) => invoke('run_channel_action', { platform, action, version }),
checkWeixinPluginStatus: () => invoke('check_weixin_plugin_status'),
// Agent 渠道绑定管理

View File

@@ -1441,7 +1441,15 @@ async function openConfigDialog(pid, page, state, accountId) {
if (progressText) progressText.textContent = `${pct}%`
})
const output = await api.runChannelAction(pid, actionId)
// 自动 pin 插件版本到用户的 OpenClaw 版本(仅 install 动作)
let actionVersion = null
if (actionId === 'install') {
try {
const vInfo = await api.getVersionInfo()
if (vInfo?.current) actionVersion = vInfo.current.split('-')[0]
} catch {}
}
const output = await api.runChannelAction(pid, actionId, actionVersion)
_flushQr() // 命令结束后刷新残留 QR 缓冲
if (progressBar) progressBar.style.width = '100%'
if (progressText) progressText.textContent = '100%'
@@ -1772,7 +1780,15 @@ async function openConfigDialog(pid, page, state, accountId) {
}
})
const output = await api.runChannelAction(pid, actionId)
// 自动 pin 插件版本(仅 install 动作)
let actionVer2 = null
if (actionId === 'install') {
try {
const vInfo = await api.getVersionInfo()
if (vInfo?.current) actionVer2 = vInfo.current.split('-')[0]
} catch {}
}
const output = await api.runChannelAction(pid, actionId, actionVer2)
toast(t('channels.actionDone'), 'success')
if (logBox && output && !String(output).includes(logBox.textContent)) {
logBox.textContent += (logBox.textContent ? '\n' : '') + String(output)
@@ -1930,11 +1946,17 @@ async function openConfigDialog(pid, page, state, accountId) {
} catch {}
try {
// 自动 pin 插件版本到用户的 OpenClaw 版本,避免 minHostVersion 不兼容
let pluginVersion = null
try {
const vInfo = await api.getVersionInfo()
if (vInfo?.current) pluginVersion = vInfo.current.split('-')[0]
} catch {}
// QQ 必须用专用安装命令:官方包目录为 openclaw-qqbot与 install_channel_plugin(…, "qqbot") 的备份路径不一致
if (pid === 'qqbot') {
await api.installQqbotPlugin()
await api.installQqbotPlugin(pluginVersion)
} else {
await api.installChannelPlugin(pluginPackage, pluginId)
await api.installChannelPlugin(pluginPackage, pluginId, pluginVersion)
}
} catch (e) {
toast(t('channels.pluginInstallFailed') + ': ' + e, 'error')