simbiot-harness/venv/lib/python3.12/site-packages/strands/tools/watcher.py
Simbiot Harness b60439152b Simbiot Harness v0.1: тактовая лестница на Strands + CQRS/ES-спина
- harness/es.py: append-only event store + проекции (образец — кооп-портал)
- harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу
- harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы
- harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе
  (глобальный asyncio-рантайм Strands не переживает 2+ create_harness)
- cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом)
- 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
2026-09-29 15:28:50 +00:00

136 lines
5.5 KiB
Python

"""Tool watcher for hot reloading tools during development.
This module provides functionality to watch tool directories for changes and automatically reload tools when they are
modified.
"""
import logging
from pathlib import Path
from typing import Any
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from .registry import ToolRegistry
logger = logging.getLogger(__name__)
class ToolWatcher:
"""Watches tool directories for changes and reloads tools when they are modified."""
# This class uses class variables for the observer and handlers because watchdog allows only one Observer instance
# per directory. Using class variables ensures that all ToolWatcher instances share a single Observer, with the
# MasterChangeHandler routing file system events to the appropriate individual handlers for each registry. This
# design pattern avoids conflicts when multiple tool registries are watching the same directories.
_shared_observer = None
_watched_dirs: set[str] = set()
_observer_started = False
_registry_handlers: dict[str, dict[int, "ToolWatcher.ToolChangeHandler"]] = {}
def __init__(self, tool_registry: ToolRegistry) -> None:
"""Initialize a tool watcher for the given tool registry.
Args:
tool_registry: The tool registry to report changes.
"""
self.tool_registry = tool_registry
self.start()
class ToolChangeHandler(FileSystemEventHandler):
"""Handler for tool file changes."""
def __init__(self, tool_registry: ToolRegistry) -> None:
"""Initialize a tool change handler.
Args:
tool_registry: The tool registry to update when tools change.
"""
self.tool_registry = tool_registry
def on_modified(self, event: Any) -> None:
"""Reload tool if file modification detected.
Args:
event: The file system event that triggered this handler.
"""
if event.src_path.endswith(".py"):
tool_path = Path(event.src_path)
tool_name = tool_path.stem
if tool_name not in ["__init__"]:
logger.debug("tool_name=<%s> | tool change detected", tool_name)
try:
self.tool_registry.reload_tool(tool_name)
except Exception as e:
logger.error("tool_name=<%s>, exception=<%s> | failed to reload tool", tool_name, str(e))
class MasterChangeHandler(FileSystemEventHandler):
"""Master handler that delegates to all registered handlers."""
def __init__(self, dir_path: str) -> None:
"""Initialize a master change handler for a specific directory.
Args:
dir_path: The directory path to watch.
"""
self.dir_path = dir_path
def on_modified(self, event: Any) -> None:
"""Delegate file modification events to all registered handlers.
Args:
event: The file system event that triggered this handler.
"""
if event.src_path.endswith(".py"):
tool_path = Path(event.src_path)
tool_name = tool_path.stem
if tool_name not in ["__init__"]:
# Delegate to all registered handlers for this directory
for handler in ToolWatcher._registry_handlers.get(self.dir_path, {}).values():
try:
handler.on_modified(event)
except Exception as e:
logger.error("exception=<%s> | handler error", str(e))
def start(self) -> None:
"""Start watching all tools directories for changes."""
# Initialize shared observer if not already done
if ToolWatcher._shared_observer is None:
ToolWatcher._shared_observer = Observer()
# Create handler for this instance
self.tool_change_handler = self.ToolChangeHandler(self.tool_registry)
registry_id = id(self.tool_registry)
# Get tools directories to watch
tools_dirs = self.tool_registry.get_tools_dirs()
for tools_dir in tools_dirs:
dir_str = str(tools_dir)
# Initialize the registry handlers dict for this directory if needed
if dir_str not in ToolWatcher._registry_handlers:
ToolWatcher._registry_handlers[dir_str] = {}
# Store this handler with its registry id
ToolWatcher._registry_handlers[dir_str][registry_id] = self.tool_change_handler
# Schedule or update the master handler for this directory
if dir_str not in ToolWatcher._watched_dirs:
# First time seeing this directory, create a master handler
master_handler = self.MasterChangeHandler(dir_str)
ToolWatcher._shared_observer.schedule(master_handler, dir_str, recursive=False)
ToolWatcher._watched_dirs.add(dir_str)
logger.debug("tools_dir=<%s> | started watching tools directory", tools_dir)
else:
# Directory already being watched, just log it
logger.debug("tools_dir=<%s> | directory already being watched", tools_dir)
# Start the observer if not already started
if not ToolWatcher._observer_started:
ToolWatcher._shared_observer.start()
ToolWatcher._observer_started = True
logger.debug("tool directory watching initialized")