1414 find_plugin_path_by_id ,
1515 get_plugin_candidate_paths ,
1616 get_plugin_config_path ,
17+ is_plugin_install_residue ,
1718 iter_plugin_directories ,
1819 load_manifest_json ,
1920 parse_repository_url ,
@@ -99,6 +100,36 @@ def _get_runtime_plugin_load_statuses() -> Dict[str, str]:
99100 return {}
100101
101102
103+ def _write_plugin_disabled_for_uninstall (plugin_path : Path ) -> None :
104+ config_path = resolve_plugin_file_path (plugin_path , "config.toml" )
105+ if config_path .exists ():
106+ with open (config_path , "r" , encoding = "utf-8" ) as file_obj :
107+ config_doc = tomlkit .load (file_obj )
108+ else :
109+ config_doc = tomlkit .document ()
110+
111+ plugin_section = config_doc .get ("plugin" )
112+ if not isinstance (plugin_section , dict ):
113+ plugin_section = tomlkit .table ()
114+ config_doc ["plugin" ] = plugin_section
115+ plugin_section ["enabled" ] = False
116+
117+ with open (config_path , "w" , encoding = "utf-8" ) as file_obj :
118+ file_obj .write (tomlkit .dumps (config_doc ))
119+
120+
121+ async def _release_plugin_runtime_before_delete (plugin_id : str , plugin_path : Path ) -> bool :
122+ try :
123+ _write_plugin_disabled_for_uninstall (plugin_path )
124+
125+ from src .plugin_runtime .integration import get_plugin_runtime_manager
126+
127+ return await get_plugin_runtime_manager ().reload_plugins_globally ([plugin_id ], reason = "uninstall" )
128+ except Exception as exc :
129+ logger .warning (f"插件 { plugin_id } 删除前运行时卸载失败,将继续尝试删除文件: { exc } " )
130+ return False
131+
132+
102133@router .post ("/install" )
103134async def install_plugin (request : InstallPluginRequest , maibot_session : Optional [str ] = Cookie (None )) -> Dict [str , Any ]:
104135 require_plugin_token (maibot_session )
@@ -121,6 +152,10 @@ async def install_plugin(request: InstallPluginRequest, maibot_session: Optional
121152 )
122153
123154 target_path , old_format_path = get_plugin_candidate_paths (plugin_id )
155+ for candidate_path in (target_path , old_format_path ):
156+ if is_plugin_install_residue (candidate_path ):
157+ logger .warning (f"检测到插件安装残留目录,安装前自动清理: { candidate_path } " )
158+ remove_tree (candidate_path )
124159 if target_path .exists () or old_format_path .exists ():
125160 await update_progress (
126161 stage = "error" ,
@@ -257,15 +292,24 @@ async def uninstall_plugin(
257292 )
258293 raise HTTPException (status_code = 404 , detail = "插件未安装" )
259294
295+ manifest = load_manifest_json (resolve_plugin_file_path (plugin_path , "_manifest.json" ))
296+ plugin_name = str (manifest .get ("name" , plugin_id )) if manifest is not None else plugin_id
297+ runtime_plugin_id = str (manifest .get ("id" , plugin_id )) if manifest is not None else plugin_id
260298 await update_progress (
261299 stage = "loading" ,
262300 progress = 30 ,
301+ message = f"正在卸载运行中的插件: { plugin_name } " ,
302+ operation = "uninstall" ,
303+ plugin_id = plugin_id ,
304+ )
305+ await _release_plugin_runtime_before_delete (runtime_plugin_id , plugin_path )
306+ await update_progress (
307+ stage = "loading" ,
308+ progress = 45 ,
263309 message = f"正在删除插件文件: { plugin_path } " ,
264310 operation = "uninstall" ,
265311 plugin_id = plugin_id ,
266312 )
267- manifest = load_manifest_json (resolve_plugin_file_path (plugin_path , "_manifest.json" ))
268- plugin_name = str (manifest .get ("name" , plugin_id )) if manifest is not None else plugin_id
269313 await update_progress (
270314 stage = "loading" ,
271315 progress = 50 ,
0 commit comments