fix(pluginmgr): 升级后恢复运行状态 —— 重装不该把正在用的插件静默停掉

## 日志实证(2026-10-20 09:54)

重装 qq 插件后,agent 的工具清单里 qq_* 全消失,它只能:

    terminal_list → get_plugin_tools ×2 → plugin_start(qq) → plugin_status
    → output_send__qq_help → get_plugin_tools → 才摸到 qq_list_stickers

根因:升级路径 `installFromData` 里 StopAndUnload 停掉旧实例后,**再也没有
把它拉回来**。安装的语义应当是"换个包",不是"把正在用的插件关掉"。

## 修法

- 升级前通过 ListPluginRuntimes 记录 `wasLoaded`
- 解压成功、skill 交接完成后,若升级前在跑则 StartPlugin 拉回
- 返回体暴露 `was_running` / `running_restored`,失败不阻断安装(插件已装好)

实测:`reinstalled qq, was_running=true, running_restored=true`,装完
loaded/alive 均为 true、PID 更新。

## 测试

- TestUpgradeRestoresRunningState:升级前在跑 ⇒ StartPlugin 恰好调用一次
- TestUpgradeDoesNotStartStoppedPlugin:升级前没跑 ⇒ 不得强行拉起

## 同批日志里的另外两条(非代码问题,仅记录)

1. llmsproxy 在 09:52:21 / 09:53:14 被重启,其间 homed 收到 connection refused,
   经「stream 失败→非流式 + 2s 重试→换 provider」兜底,用户侧无失败但多耗一轮。
2. 上游返回 404「The model is not registered」与 429 限流(channels-codearts),
   属 llmsproxy 侧渠道配置/配额问题,建议在 llmsproxy 里核对该模型注册。
This commit is contained in:
JianFeeeee
2026-10-20 10:01:59 +08:00
parent 276553727f
commit 679536692f
2 changed files with 105 additions and 2 deletions

View File

@ -733,6 +733,19 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
}
if existing && overwrite {
// ★ 先记住升级前是否在运行:StopAndUnload 会把它停掉,而升级不该
// 顺手改变运行状态。生产实测(2026-10-20):重装 qq 后它静默停着,
// agent 发现工具全没了,只能 get_plugin_tools×N → plugin_start 摸回来。
wasLoaded := false
if p.sdk != nil && p.sdk.PluginMgr() != nil {
for _, rt := range p.sdk.PluginMgr().ListPluginRuntimes() {
if rt.Name == pkg.Name && rt.Loaded {
wasLoaded = true
break
}
}
}
// 原地更新:停旧实例(保留配置表),备份旧目录,解压新包,失败回滚。
if p.sdk != nil && p.sdk.PluginMgr() != nil {
if err := p.sdk.PluginMgr().StopAndUnload(pkg.Name); err != nil {
@ -771,6 +784,17 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
// 失败不阻断 —— skill 登记是附带收益,插件本身已装好。
skillN, skillFailed := p.handOutPluginSkills(target, pkg.Name)
// 升级前在跑的插件,升级后自动拉回来(运行状态不该被"换个包"改变)。
// 失败不阻断安装:返回里如实写 running_restored=false,调用方可见。
runningRestored := false
if wasLoaded && p.sdk != nil && p.sdk.PluginMgr() != nil {
if err := p.sdk.PluginMgr().StartPlugin(pkg.Name); err != nil {
log.Printf("[pluginmgr] 升级后恢复运行 %s 失败: %v(插件已安装,可手动 plugin_start)", pkg.Name, err)
} else {
runningRestored = true
}
}
checksum := fmt.Sprintf("%x", sha256.Sum256(data))
action := "upgraded"
if cmpVersion(pkg.Version, oldVersion) < 0 {
@ -788,6 +812,8 @@ func (p *Plugin) installFromData(data []byte, overwrite bool) (interface{}, erro
"action": action,
"reload_required": true,
"config_kept": true,
"was_running": wasLoaded,
"running_restored": runningRestored,
"skills_registered": skillN,
"skills_failed": skillFailed,
}, nil