fix(acp): pin @latest for all plugins and enable button when only floating versions exist

- install_missing_plugins now pins ALL @latest specs (not just newly installed)
- When nothing is missing but @latest exists, the pin-only path runs
- Modal button switches to 'Pin @latest Versions' when no missing plugins
- Added pinVersions i18n key for 10 languages
This commit is contained in:
xintaofei
2026-04-12 11:56:55 +08:00
parent 4397b0eae7
commit 023e4c5514
12 changed files with 60 additions and 9 deletions

View File

@@ -435,12 +435,44 @@ pub async fn install_missing_plugins(
.collect(); .collect();
if missing.is_empty() { if missing.is_empty() {
emit_plugin_event( // Nothing to install, but still pin any @latest specs
emitter, let all_specs: Vec<(String, String)> = summary
&task_id, .plugins
PluginInstallEventKind::Completed, .iter()
"Nothing to install — all plugins are already present", .map(|p| (p.name.clone(), p.declared_spec.clone()))
); .collect();
match pin_latest_specs(&summary.config_path, &summary.cache_dir, &all_specs) {
Ok(n) if n > 0 => {
emit_plugin_event(
emitter,
&task_id,
PluginInstallEventKind::Log,
format!("Pinned {n} @latest plugin(s) to installed versions in opencode.json"),
);
emit_plugin_event(
emitter,
&task_id,
PluginInstallEventKind::Completed,
format!("Pinned {n} @latest plugin(s) — no missing plugins to install"),
);
}
Err(e) => {
emit_plugin_event(
emitter,
&task_id,
PluginInstallEventKind::Failed,
format!("Failed to pin @latest versions: {e}"),
);
}
_ => {
emit_plugin_event(
emitter,
&task_id,
PluginInstallEventKind::Completed,
"Nothing to install — all plugins are already present",
);
}
}
return Ok(()); return Ok(());
} }
@@ -518,7 +550,10 @@ pub async fn install_missing_plugins(
if exit_status.success() { if exit_status.success() {
// Pin @latest specs to actual installed versions to avoid // Pin @latest specs to actual installed versions to avoid
// opencode hitting the npm registry on every startup. // opencode hitting the npm registry on every startup.
let spec_pairs: Vec<(String, String)> = missing // Pin ALL plugins (not just the ones we installed), so already-installed
// @latest plugins also get pinned.
let spec_pairs: Vec<(String, String)> = summary
.plugins
.iter() .iter()
.map(|p| (p.name.clone(), p.declared_spec.clone())) .map(|p| (p.name.clone(), p.declared_spec.clone()))
.collect(); .collect();

View File

@@ -117,6 +117,10 @@ export function OpencodePluginsModal({
const missingCount = const missingCount =
summary?.plugins.filter((p) => p.status === "missing").length ?? 0 summary?.plugins.filter((p) => p.status === "missing").length ?? 0
const floatingCount =
summary?.plugins.filter((p) => p.declared_spec.endsWith("@latest"))
.length ?? 0
const hasActionablePlugins = missingCount > 0 || floatingCount > 0
return ( return (
<Dialog open={open} onOpenChange={handleClose}> <Dialog open={open} onOpenChange={handleClose}>
@@ -209,7 +213,7 @@ export function OpencodePluginsModal({
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
size="sm" size="sm"
disabled={isOperating || missingCount === 0} disabled={isOperating || !hasActionablePlugins}
onClick={handleInstallAll} onClick={handleInstallAll}
> >
{stream.status === "running" ? ( {stream.status === "running" ? (
@@ -217,7 +221,9 @@ export function OpencodePluginsModal({
) : ( ) : (
<Download className="h-3.5 w-3.5 mr-1.5" /> <Download className="h-3.5 w-3.5 mr-1.5" />
)} )}
{t("opencodePlugins.installAll")} {missingCount > 0
? t("opencodePlugins.installAll")
: t("opencodePlugins.pinVersions")}
</Button> </Button>
<Button <Button
size="sm" size="sm"

View File

@@ -695,6 +695,7 @@
"missing": "غير مثبّت" "missing": "غير مثبّت"
}, },
"installAll": "تثبيت جميع الإضافات المفقودة", "installAll": "تثبيت جميع الإضافات المفقودة",
"pinVersions": "تثبيت إصدارات @latest",
"install": "تثبيت", "install": "تثبيت",
"uninstall": "إزالة", "uninstall": "إزالة",
"refresh": "تحديث", "refresh": "تحديث",

View File

@@ -695,6 +695,7 @@
"missing": "Nicht installiert" "missing": "Nicht installiert"
}, },
"installAll": "Alle fehlenden installieren", "installAll": "Alle fehlenden installieren",
"pinVersions": "@latest-Versionen fixieren",
"install": "Installieren", "install": "Installieren",
"uninstall": "Deinstallieren", "uninstall": "Deinstallieren",
"refresh": "Aktualisieren", "refresh": "Aktualisieren",

View File

@@ -695,6 +695,7 @@
"missing": "Missing" "missing": "Missing"
}, },
"installAll": "Install All Missing", "installAll": "Install All Missing",
"pinVersions": "Pin @latest Versions",
"install": "Install", "install": "Install",
"uninstall": "Uninstall", "uninstall": "Uninstall",
"refresh": "Refresh", "refresh": "Refresh",

View File

@@ -695,6 +695,7 @@
"missing": "No instalado" "missing": "No instalado"
}, },
"installAll": "Instalar todos los faltantes", "installAll": "Instalar todos los faltantes",
"pinVersions": "Fijar versiones @latest",
"install": "Instalar", "install": "Instalar",
"uninstall": "Desinstalar", "uninstall": "Desinstalar",
"refresh": "Actualizar", "refresh": "Actualizar",

View File

@@ -695,6 +695,7 @@
"missing": "Non installé" "missing": "Non installé"
}, },
"installAll": "Installer tous les manquants", "installAll": "Installer tous les manquants",
"pinVersions": "Fixer les versions @latest",
"install": "Installer", "install": "Installer",
"uninstall": "Désinstaller", "uninstall": "Désinstaller",
"refresh": "Actualiser", "refresh": "Actualiser",

View File

@@ -695,6 +695,7 @@
"missing": "未インストール" "missing": "未インストール"
}, },
"installAll": "不足プラグインをすべてインストール", "installAll": "不足プラグインをすべてインストール",
"pinVersions": "@latest バージョンを固定",
"install": "インストール", "install": "インストール",
"uninstall": "アンインストール", "uninstall": "アンインストール",
"refresh": "更新", "refresh": "更新",

View File

@@ -695,6 +695,7 @@
"missing": "미설치" "missing": "미설치"
}, },
"installAll": "누락된 플러그인 모두 설치", "installAll": "누락된 플러그인 모두 설치",
"pinVersions": "@latest 버전 고정",
"install": "설치", "install": "설치",
"uninstall": "제거", "uninstall": "제거",
"refresh": "새로고침", "refresh": "새로고침",

View File

@@ -695,6 +695,7 @@
"missing": "Não instalado" "missing": "Não instalado"
}, },
"installAll": "Instalar todos os ausentes", "installAll": "Instalar todos os ausentes",
"pinVersions": "Fixar versões @latest",
"install": "Instalar", "install": "Instalar",
"uninstall": "Desinstalar", "uninstall": "Desinstalar",
"refresh": "Atualizar", "refresh": "Atualizar",

View File

@@ -695,6 +695,7 @@
"missing": "未安装" "missing": "未安装"
}, },
"installAll": "安装全部缺失插件", "installAll": "安装全部缺失插件",
"pinVersions": "固定 @latest 版本",
"install": "安装", "install": "安装",
"uninstall": "卸载", "uninstall": "卸载",
"refresh": "刷新", "refresh": "刷新",

View File

@@ -695,6 +695,7 @@
"missing": "未安裝" "missing": "未安裝"
}, },
"installAll": "安裝全部缺失外掛", "installAll": "安裝全部缺失外掛",
"pinVersions": "固定 @latest 版本",
"install": "安裝", "install": "安裝",
"uninstall": "解除安裝", "uninstall": "解除安裝",
"refresh": "重新整理", "refresh": "重新整理",