diff --git a/CHANGELOG.md b/CHANGELOG.md index 9918670..d6bd765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0] - 2026-01-26 + +### Changed +- Simplified `WatchPoint` node: Removed internal debug systems and complex logging. +- Integrated text display functionality directly into `WatchPoint` node (replaced Signal Scout). +- Refactored `WindowManager` for better performance while maintaining thread safety. + +### Removed +- `WatchPointDebugToggle` node and associated debug logic. +- `WatchPointRestoreWindow` node (functionality cleaned up). +- `WPSignalScout` node (functionality merged). +- `nodes/` directory and utility files to reduce codebase size. + ## [1.0.0] - 2026-01-24 ### Added diff --git a/README.md b/README.md index 120ff35..333ab20 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ Watch Point provides two simultaneous preview methods: - Save images directly from preview - Copy to clipboard - Customizable settings -- Signal Scout Panel: Display text from your workflow in a side panel. - Thread Safety: Windows minimize instead of closing to prevent ComfyUI crashes ### Floating Preview (JavaScript) @@ -93,52 +92,13 @@ The main preview node. 3. Enable/disable previews: * `floating_preview`: Enable ComfyUI's built-in preview. * `monitor_preview`: Enable the external monitor window. - - 4. Select the target `monitor` from the dropdown. 5. The node acts as a pass-through for the `IMAGE` output. -### WP Signal Scout +### Optional string input -A debug node to send text to the Monitor Preview window. - -1. Add **WP Signal Scout** to your workflow (found in `WatchPoint/Debug`). -2. Connect any `STRING` input. -3. The text will appear in the side panel of the external window. - -### Utility Nodes (Optional) - -These nodes provide additional functionality and can be enabled/disabled via configuration. - -#### WatchPoint Debug Toggle - -A simple node to enable/disable persistent debug mode for WatchPoint nodes. - -* **Function**: Toggle debug mode with a single click -* **Location**: `WatchPoint/Utils` (when enabled) -* **Usage**: Connect to any workflow to activate debug logging - -#### WatchPoint Restore Window - -A utility node to restore minimized WatchPoint windows. - -* **Function**: Recover hidden/minimized windows -* **Location**: `WatchPoint/Utils` (when enabled) -* **Usage**: Add to workflow when windows need restoration - -**Note**: Utility nodes are loaded optionally. To enable/disable them, modify the `__init__.py` file. - -### Example Workflow - -``` -[Primitive] → [WP Signal Scout] - (text) ↓ (sends text to window) - -[Load Image] → [Watch Point] → [Upscale] → [Save Image] - ↓ - (Dual Preview) - -``` +1. Connect any `STRING` input. +2. The text will appear in the side panel of the external window. --- @@ -154,16 +114,14 @@ A utility node to restore minimized WatchPoint windows. * **R**: Reset zoom and pan * **T**: Toggle toolbar -* **P**: Toggle Signal Scout panel +* **P**: Toggle Text panel * **1**: Zoom 1:1 (100% actual size) -* **ESC**: Close window ### Window Close Behavior **Important**: The Windows close button (❌) minimizes the window instead of closing it. This prevents threading errors and keeps ComfyUI stable. To "close" the window: * Click the close button → Window minimizes to taskbar -* Use **ESC** key to properly close the window * Right-click taskbar icon → "Close window" for forced closure ### Toolbar Buttons @@ -182,22 +140,6 @@ A utility node to restore minimized WatchPoint windows. Access settings via the **⚙ Settings** button in the toolbar. -### Window Size - -* Fixed sizes: 800x600, 1024x768, 1280x720, 1920x1080 -* Dynamic sizes: -* **Half Vertical**: Half screen width, full height -* **Half Horizontal**: Full width, half screen height -* **Quarter**: Half width, half height - - - -### Window Position - -* Set exact X, Y coordinates for window placement -* Leave empty for automatic positioning -* Example: X=0, Y=0 for top-left corner - ### Save Options * Default format: PNG, JPEG, or WebP @@ -211,33 +153,6 @@ All settings are saved to `watchpoint_settings.json` --- -## 🔧 Utility Nodes Configuration - -Utility nodes (WatchPoint Debug Toggle and WatchPoint Restore Window) are optional and can be enabled/disabled: - -### Enabling Utility Nodes - -1. Open `__init__.py` in the ComfyUI-WatchPoint folder -2. Ensure the import block is uncommented: - -```python -try: - from .nodes.watchpoint_utils import NODE_CLASS_MAPPINGS as UTILS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as UTILS_DISPLAY - NODE_CLASS_MAPPINGS.update(UTILS_CLASS) - NODE_DISPLAY_NAME_MAPPINGS.update(UTILS_DISPLAY) - print("WatchPoint Utils loaded: Debug Toggle and Restore Window available") -except ImportError as e: - print(f"WatchPoint Utils not available: {e}") - pass - -``` - -### Disabling Utility Nodes - -Comment out or remove the import block in `__init__.py` to disable utility nodes. - ---- - ## 🔧 Configuration Files ### Monitor Preview Settings @@ -516,6 +431,5 @@ MIT License - Feel free to use and modify! ### v1.0.0 - Initial Release * Dual preview system (Monitor + Floating) -* Signal Scout text display * Basic window controls and settings * Multi-monitor support diff --git a/__init__.py b/__init__.py index fa43e6d..b2d3940 100644 --- a/__init__.py +++ b/__init__.py @@ -1,22 +1,7 @@ -from .watch_point import NODE_CLASS_MAPPINGS as WP_CLASS, NODE_DISPLAY_NAME_MAPPINGS as WP_DISPLAY, cleanup_all_watchpoints - -# Combine the basics -NODE_CLASS_MAPPINGS = {**WP_CLASS} -NODE_DISPLAY_NAME_MAPPINGS = {**WP_DISPLAY} - -# Import utility nodes optionally (can be disabled if not needed) -try: - from .nodes.watchpoint_utils import NODE_CLASS_MAPPINGS as UTILS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as UTILS_DISPLAY - # Add utility nodes to the mappings - NODE_CLASS_MAPPINGS.update(UTILS_CLASS) - NODE_DISPLAY_NAME_MAPPINGS.update(UTILS_DISPLAY) - print("WatchPoint Utils loaded: Debug Toggle and Restore Window available") -except ImportError as e: - print(f"WatchPoint Utils not available: {e}") - pass # Utility nodes are optional, not a critical error +from .watch_point import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS, cleanup_all_watchpoints # JS is needed for the floating preview functionality WEB_DIRECTORY = "js" # Export the cleanup function for external use -__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY", "cleanup_all_watchpoints"] \ No newline at end of file +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY", "cleanup_all_watchpoints"] diff --git a/nodes/signal_scout.py b/nodes/signal_scout.py deleted file mode 100644 index 22b424c..0000000 --- a/nodes/signal_scout.py +++ /dev/null @@ -1,29 +0,0 @@ -# nodes/signal_scout.py -from server import PromptServer - -class WPSignalScout: - @classmethod - def INPUT_TYPES(s): - return { - "required" : { - "text": ("STRING", {"forceInput": True }), - }, - } - - RETURN_TYPES = ("STRING" ,) - RETURN_NAMES = ("text" ,) - FUNCTION = "scout_signal" - CATEGORY = "WatchPoint/Debug" - - def scout_signal(self, text): - # Try to import WatchPoint from root file - try : - from ..watch_point import WatchPoint - WatchPoint.update_all_text(text) - except Exception as e: - print(f"WP_Scout Error: Could not connect to window: {e}" ) - - return (text,) - -NODE_CLASS_MAPPINGS = {"WPSignalScout" : WPSignalScout} -NODE_DISPLAY_NAME_MAPPINGS = {"WPSignalScout": "📡 WP Signal Scout" } \ No newline at end of file diff --git a/nodes/watchpoint_utils.py b/nodes/watchpoint_utils.py deleted file mode 100644 index e7efc2d..0000000 --- a/nodes/watchpoint_utils.py +++ /dev/null @@ -1,74 +0,0 @@ -import json -import os -from server import PromptServer -from aiohttp import web -import sys -sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) - -try: - from watch_point import wp_logger, window_manager -except ImportError: - wp_logger = None - window_manager = None - -DEBUG = True - -# WatchPoint Debug Toggle Node -class WatchPointDebugToggle: - """Simple node to enable/disable persistent debug mode - One click!""" - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "debug_activado": ("BOOLEAN", {"default": False}), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "toggle_debug" - CATEGORY = "WatchPoint/Utils" - - def toggle_debug(self, debug_activado): - if wp_logger: - wp_logger.set_debug_mode(debug_activado) - if debug_activado: - return ("✅ Persistent Debug ACTIVATED\n💾 Dumps will be saved automatically on each execution",) - else: - return ("⚪ Persistent Debug DEACTIVATED\n📝 Dumps will be saved manually",) - -# WatchPoint Restore Window Node -class WatchPointRestoreWindow: - """Node to restore minimized windows - Recover your hidden windows!""" - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "display_idx": ("INT", {"default": 0, "min": 0, "max": 10}), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "restore_window" - CATEGORY = "WatchPoint/Utils" - - def restore_window(self, display_idx): - if window_manager and hasattr(window_manager, 'restore_window'): - success = window_manager.restore_window(display_idx) - if success: - return (f"✅ Window {display_idx} restored successfully\n🪟 Window came back to life!",) - else: - return (f"❌ Could not restore window {display_idx}\n📝 Check that it exists and is minimized",) - else: - return (f"⚠️ WindowManager not available\n📝 Cannot restore window",) - -NODE_CLASS_MAPPINGS = { - "WatchPointDebugToggle": WatchPointDebugToggle, - "WatchPointRestoreWindow": WatchPointRestoreWindow, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "WatchPointDebugToggle": "🔨 WatchPoint Debug Toggle", - "WatchPointRestoreWindow": "🪟 WatchPoint Restore Window", -} \ No newline at end of file diff --git a/watch_point.py b/watch_point.py index 7d1b58a..56ecc67 100644 --- a/watch_point.py +++ b/watch_point.py @@ -46,169 +46,23 @@ class ShutdownRegistry: # Create global instance shutdown_registry = ShutdownRegistry() -# Logging Structured +# Logging Structured (Simplified) class WatchPointLogger: - """Structured logging system for WatchPoint""" + """Simple logging system for WatchPoint""" def __init__(self): self.enabled = True - self.log_level = "INFO" # DEBUG, INFO, WARNING, ERROR - self.logs = [] - self.max_logs = 100 - self.debug_mode = False - self.debug_log_dir = "debug_logs" - self.debug_session_id = None - self.config_file = os.path.join(os.path.dirname(__file__), "watchpoint_debug_config.json") - self.load_debug_config() def log(self, level, message, component="WatchPoint"): - """Log a message with level and component""" - if not self.enabled: - return - - # Check log level - levels = {"DEBUG": 0, "INFO": 1, "WARNING": 2, "ERROR": 3} - if levels.get(level, 1) < levels.get(self.log_level, 1): - return - + if not self.enabled: return timestamp = time.strftime("%Y-%m-%d %H:%M:%S") - log_entry = { - "timestamp": timestamp, - "level": level, - "component": component, - "message": message - } - - self.logs.append(log_entry) - - # Keep only the latest logs - if len(self.logs) > self.max_logs: - self.logs.pop(0) - - # Always print errors - if level == "ERROR": - print(f"WatchPoint [{timestamp}] {level}: {message}") - elif level == "WARNING": + if level in ["ERROR", "WARNING"]: print(f"WatchPoint [{timestamp}] {level}: {message}") - def debug(self, message, component="WatchPoint"): - self.log("DEBUG", message, component) - - def info(self, message, component="WatchPoint"): - self.log("INFO", message, component) - - def warning(self, message, component="WatchPoint"): - self.log("WARNING", message, component) - - def error(self, message, component="WatchPoint"): - self.log("ERROR", message, component) - - def get_logs(self, level=None, component=None): - """Get filtered logs by level and component""" - filtered_logs = self.logs - - if level: - filtered_logs = [log for log in filtered_logs if log["level"] == level] - - if component: - filtered_logs = [log for log in filtered_logs if log["component"] == component] - - return filtered_logs - - def clear_logs(self): - """Clear all logs""" - self.logs = [] - - def set_debug_mode(self, enabled=True): - """Enable/Disable debug mode""" - self.debug_mode = enabled - if enabled: - self.debug_session_id = time.strftime("%Y%m%d_%H%M%S") - - os.makedirs(self.debug_log_dir, exist_ok=True) - self.info(f"Debug mode enabled - Session: {self.debug_session_id}", "Debug") - else: - self.info("Debug mode disabled", "Debug") - - - self.save_debug_config() - - def load_debug_config(self): - """Load debug configuration from file""" - try: - if os.path.exists(self.config_file): - with open(self.config_file, 'r') as f: - config = json.load(f) - self.debug_mode = config.get('debug_mode', False) - if self.debug_mode: - self.debug_session_id = time.strftime("%Y%m%d_%H%M%S") - self.info(f"Persistent debug mode activated - Session: {self.debug_session_id}", "Debug") - except Exception as e: - self.error(f"Error loading debug config: {e}", "Debug") - - def save_debug_config(self): - """Save debug configuration to file""" - try: - config = { - 'debug_mode': self.debug_mode, - 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S") - } - with open(self.config_file, 'w') as f: - json.dump(config, f, indent=2) - except Exception as e: - self.error(f"Error saving debug config: {e}", "Debug") - - def save_debug_dump(self, watch_point_instance=None): - """Save complete debug dump with stats, threads, and logs""" - if not self.debug_mode: - return - - # Prevent infinite recursion - if hasattr(self, '_saving_dump') and self._saving_dump: - return None - - try: - self._saving_dump = True - timestamp = time.strftime("%Y%m%d_%H%M%S") - filename = f"debug_dump_{self.debug_session_id}_{timestamp}.log" - filepath = os.path.join(self.debug_log_dir, filename) - - debug_data = { - "timestamp": timestamp, - "session_id": self.debug_session_id, - "system_info": { - "python_version": sys.version, - "platform": sys.platform, - "threading_active": threading.active_count() - } - } - - - if watch_point_instance and hasattr(watch_point_instance, 'get_health_stats'): - debug_data["health_stats"] = watch_point_instance.get_health_stats() - - - if watch_point_instance and hasattr(watch_point_instance, 'debug_threads'): - # Call the function directly without going through the logger to avoid recursion - debug_data["thread_info"] = watch_point_instance.debug_threads() - - - debug_data["recent_logs"] = self.get_logs()[-50:] # Last 50 logs - - # Save to file - with open(filepath, 'w', encoding='utf-8') as f: - import json - json.dump(debug_data, f, indent=2, default=str) - - self.info(f"Debug dump saved: {filename}", "Debug") - return filepath - - except Exception as e: - self.error(f"Error saving debug dump: {e}", "Debug") - return None - finally: - # Clear recursion flag - self._saving_dump = False + def debug(self, message, component="WatchPoint"): pass # Debug silenced + def info(self, message, component="WatchPoint"): self.log("INFO", message, component) + def warning(self, message, component="WatchPoint"): self.log("WARNING", message, component) + def error(self, message, component="WatchPoint"): self.log("ERROR", message, component) # Create global logger wp_logger = WatchPointLogger() @@ -415,26 +269,14 @@ class WindowManager: try: root.mainloop() except RuntimeError as e: - # Common RuntimeError when closing: "main thread is not in main loop" if "main thread" not in str(e): wp_logger.error(f"RuntimeError in mainloop: {e}", "WindowLoop") - # Trigger debug dump on critical error - if wp_logger.debug_mode: - wp_logger.save_debug_dump() raise - else: - wp_logger.debug(f"Expected RuntimeError in mainloop: {e}", "WindowLoop") except Exception as e: - # Trigger debug dump on thread errors (Tcl_AsyncDelete) - if wp_logger.debug_mode and ("Tcl_AsyncDelete" in str(e) or "async handler" in str(e)): - wp_logger.save_debug_dump() wp_logger.error(f"Error in Tkinter mainloop: {e}", "WindowLoop") except Exception as e: wp_logger.error(f"Error in window {display_idx}: {e}", "WindowLoop") - # Trigger debug dump on critical window error - if wp_logger.debug_mode: - wp_logger.save_debug_dump() finally: # IMPROVED: Cleanup with multiple attempts and Tkinter cleanup @@ -475,40 +317,6 @@ class WindowManager: wp_logger.error(f"Cleanup failed after 3 attempts: {e}", "WindowLoop") time.sleep(0.05) - def restore_window(self, display_idx): - """Restore a minimized window - To recover it from the taskbar!""" - if display_idx in self.windows: - win_data = self.windows[display_idx] - - # Check if it's minimized - if not win_data.get("minimized", False): - wp_logger.debug(f"Window {display_idx} is not minimized", "RestoreWindow") - return False - - instance = win_data.get("instance") - if not instance: - wp_logger.warning(f"Cannot restore window {display_idx}: invalid instance", "RestoreWindow") - return False - - try: - # Restore the window using the instance's root - if hasattr(instance, 'root') and instance.root: - instance.root.deiconify() # Make visible again - instance.root.lift() # Bring to front - instance.root.focus_force() # Give focus - - # Update state - win_data["minimized"] = False - wp_logger.info(f"Window {display_idx} restored successfully", "RestoreWindow") - return True - - except Exception as e: - wp_logger.error(f"Error restoring window {display_idx}: {e}", "RestoreWindow") - return False - else: - wp_logger.warning(f"Window {display_idx} does not exist", "RestoreWindow") - return False - def _apply_icon(self, root): """Applies the icon from a file to the window.""" try: @@ -641,23 +449,6 @@ class WindowManager: except Exception as e: wp_logger.error(f"Error in force cleanup: {e}", "Cleanup") - def get_health_stats(self): - """Get health statistics of the system""" - stats = { - "total_windows_created": len(self.windows), - "active_windows": len([w for w in self.windows.values() if w.get("running", False)]), - "closing_windows": len([w for w in self.windows.values() if w.get("closing", False)]), - "threads_alive": len([w for w in self.windows.values() if w.get("thread") and w["thread"].is_alive()]), - "watchdog_status": "running" if hasattr(self, 'watchdog_thread') and self.watchdog_thread.is_alive() else "stopped", - "shutdown_event": self.shutdown_event.is_set(), - "uptime": time.time() - getattr(self, '_start_time', time.time()) - } - - # Add thread information - import threading - stats["total_threads"] = threading.active_count() - - return stats def shutdown(self): """Shutdown global del WindowManager without using atexit""" wp_logger.info("Initiating global shutdown...", "WindowManager") @@ -736,11 +527,6 @@ class WatchPoint: ui_images = self._prepare_preview(images) if floating_preview else [] - # NEW: Automatically save dump if debug is enabled - if wp_logger.debug_mode: - wp_logger.info("Saving automatic dump - WatchPoint executed", "WatchPoint") - wp_logger.save_debug_dump(self) - return {"ui": {"images": ui_images}, "result": (images,)} def _prepare_preview(self, images): @@ -761,90 +547,6 @@ class WatchPoint: """Cleanup del nodo WatchPoint sin usar atexit""" if hasattr(self, 'window_manager'): self.window_manager.shutdown() - - def get_logs(self, level=None, component=None): - """Get system logging logs""" - return wp_logger.get_logs(level, component) - - def clear_logs(self): - """Clear all logs""" - wp_logger.clear_logs() - wp_logger.info("Logs cleared", "WatchPoint") - - def set_debug_mode(self, enabled=True): - """Enable/Disable debug mode""" - wp_logger.set_debug_mode(enabled) - if enabled: - wp_logger.info("Debug mode enabled for WatchPoint", "WatchPoint") - else: - wp_logger.info("Debug mode disabled for WatchPoint", "WatchPoint") - - def save_debug_dump(self): - """Save complete debug dump""" - filepath = wp_logger.save_debug_dump(self) - if filepath: - wp_logger.info(f"Debug dump saved at: {filepath}", "WatchPoint") - return filepath - - def get_health_stats(self): - """Get system health statistics""" - return self.window_manager.get_health_stats() - - def debug_threads(self): - """Debug command to inspect threads""" - import threading - - debug_info = { - "total_threads": threading.active_count(), - "threads": [], - "window_threads": [], - "daemon_threads": [] - } - - # Inspect all threads - for thread in threading.enumerate(): - thread_info = { - "name": thread.name, - "ident": thread.ident, - "is_alive": thread.is_alive(), - "is_daemon": thread.daemon, - "thread_type": type(thread).__name__ - } - - debug_info["threads"].append(thread_info) - - if thread.daemon: - debug_info["daemon_threads"].append(thread_info) - - # Inspect window threads - if hasattr(self, 'window_manager') and hasattr(self.window_manager, 'windows'): - for window_id, window_data in self.window_manager.windows.items(): - if window_data.get('thread'): - thread = window_data['thread'] - window_thread_info = { - "window_id": window_id, - "thread_name": thread.name, - "thread_ident": thread.ident, - "is_alive": thread.is_alive(), - "running": window_data.get('running', False), - "closing": window_data.get('closing', False) - } - debug_info["window_threads"].append(window_thread_info) - - # Add watchdog information - if hasattr(self.window_manager, 'watchdog_thread'): - watchdog = self.window_manager.watchdog_thread - debug_info["watchdog"] = { - "name": watchdog.name, - "ident": watchdog.ident, - "is_alive": watchdog.is_alive(), - "is_daemon": watchdog.daemon - } - - wp_logger.info(f"Debug threads executed: {debug_info['total_threads']} active threads, " - f"{len(debug_info['window_threads'])} window threads, " - f"{len(debug_info['daemon_threads'])} daemon threads", "WatchPoint") - return debug_info # Tkinter UI Classes class WatchPointWindow: @@ -1158,22 +860,6 @@ class WatchPointSettingsDialog: NODE_CLASS_MAPPINGS = {"WatchPoint": WatchPoint} NODE_DISPLAY_NAME_MAPPINGS = {"WatchPoint": "👁️ Watch Point"} -# Signal Scout Integration -class WPSignalScout: - """A simple node to send text to all Watch Point windows.""" - @classmethod - def INPUT_TYPES(cls): - return {"required": {"text": ("STRING", {"multiline": True, "default": ""})}} - RETURN_TYPES = ("STRING",) - FUNCTION = "scout_signal" - CATEGORY = "WatchPoint" - - def scout_signal(self, text): - try: - WindowManager().update_all_text(text) - except Exception as e: - print(f"WP_Scout Error: Could not connect to Watch Point window: {e}") - # Global Shutdown Function def cleanup_all_watchpoints(): """Function to clean up all WatchPoint nodes without using atexit""" @@ -1181,76 +867,3 @@ def cleanup_all_watchpoints(): # Add the function to the module so it's available __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "cleanup_all_watchpoints"] - -NODE_CLASS_MAPPINGS["WPSignalScout"] = WPSignalScout -NODE_DISPLAY_NAME_MAPPINGS["WPSignalScout"] = "📡 WP Signal Scout" - -# Debug Node -class WatchPointDebug: - """Debug node for WatchPoint - Controls debug mode and captures dumps""" - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "action": (["enable_debug", "disable_debug", "save_dump", "get_stats", "get_threads"],), - } - } - - RETURN_TYPES = ("STRING",) - FUNCTION = "execute_debug_action" - CATEGORY = "WatchPoint" - - def execute_debug_action(self, action): - """Execute debug action and return result""" - - if action == "enable_debug": - wp_logger.set_debug_mode(True) - return ("✅ Debug mode enabled",) - - elif action == "disable_debug": - wp_logger.set_debug_mode(False) - return ("⚪ Debug mode disabled",) - - elif action == "save_dump": - # Find an active WatchPoint instance - watchpoint_instance = self._find_watchpoint_instance() - filepath = wp_logger.save_debug_dump(watchpoint_instance) - if filepath: - return (f"💾 Debug dump saved: {os.path.basename(filepath)}",) - else: - return ("❌ Error saving debug dump (debug mode disabled?)",) - - elif action == "get_stats": - watchpoint_instance = self._find_watchpoint_instance() - if watchpoint_instance and hasattr(watchpoint_instance, 'get_health_stats'): - stats = watchpoint_instance.get_health_stats() - stats_text = json.dumps(stats, indent=2, default=str) - return (f"📊 Health Statistics:\n{stats_text}",) - else: - return ("ℹ️ No active WatchPoint instance found",) - - elif action == "get_threads": - watchpoint_instance = self._find_watchpoint_instance() - if watchpoint_instance and hasattr(watchpoint_instance, 'debug_threads'): - threads = watchpoint_instance.debug_threads() - threads_text = json.dumps(threads, indent=2, default=str) - return (f"🧵 Thread Information:\n{threads_text}",) - else: - return ("ℹ️ No active WatchPoint instance found",) - - return ("❌ Action not recognized",) - - def _find_watchpoint_instance(self): - """Find an active WatchPoint instance in the global registry""" - try: - for node in shutdown_registry._nodes: - if isinstance(node, WatchPoint): - return node - except Exception: - pass - return None - -# Add the debug node to mappings -NODE_CLASS_MAPPINGS["WatchPointDebug"] = WatchPointDebug -NODE_DISPLAY_NAME_MAPPINGS["WatchPointDebug"] = "🔧 Watch Point Debug" \ No newline at end of file