fix(plugin-system): load/enable failures, atomic state files, pip lock, test-double parity (#645)

- load_plugin: an on_enable() that raises unregisters the instance, so the
  next load retries instead of returning True "already loaded".
- get_plugin_info: guard plugin.get_info(); one plugin raising no longer
  breaks /api/v3/plugins/installed.
- plugin_state.json and the operation history are written with
  atomic_write_text under their lock.
- plugin_loader: module-level lock serialises pip installs across the
  parallel startup loaders.
- store_manager._install_via_download: extract dir cleanup moved to finally.
- Test doubles: draw_image() warns (DeprecationWarning; the real
  DisplayManager has none), MockDisplayManager.draw_text accepts the real
  signature's optional params, VisualTestDisplayManager logs draw errors at
  WARNING.
- Docs/comments: compatibility.py method name, PluginState.LOADED meaning,
  brittle schema count, why _report_skip_once uses setdefault.
- Remove unused PluginOperationQueue.get_active_operations().

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-28 08:25:45 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 6bc13a8934
commit b518c51679
16 changed files with 507 additions and 130 deletions
+8 -6
View File
@@ -11,6 +11,7 @@ from datetime import datetime
from pathlib import Path
from dataclasses import dataclass, asdict
from src.config_manager_atomic import atomic_write_text
from src.logging_config import get_logger
@@ -180,15 +181,16 @@ class OperationHistory:
return
try:
# Held across the write, and written via a temp file, so two
# threads saving at once can't interleave or truncate the file.
with self._lock:
history_data = [record.to_dict() for record in self._history]
# Ensure directory exists
self.history_file.parent.mkdir(parents=True, exist_ok=True)
# Write to file
with open(self.history_file, 'w') as f:
json.dump(history_data, f, indent=2)
# Ensure directory exists
self.history_file.parent.mkdir(parents=True, exist_ok=True)
# Write to file
atomic_write_text(self.history_file, json.dumps(history_data, indent=2))
except Exception as e:
self.logger.error(f"Error saving operation history: {e}", exc_info=True)