diff --git a/hermes_time.py b/hermes_time.py index ac833cec54..26bcea13de 100644 --- a/hermes_time.py +++ b/hermes_time.py @@ -61,16 +61,6 @@ def _resolve_timezone_name() -> str: return "" -def _get_zoneinfo(name: str) -> Optional[ZoneInfo]: - if not name: - return None - try: - return ZoneInfo(name) - except Exception as exc: - logger.warning("Invalid timezone '%s': %s. Falling back to server local time.", name, exc) - return None - - def get_timezone() -> Optional[ZoneInfo]: """Return the active profile's configured ZoneInfo, or None (server-local).""" cache_identity = _timezone_cache_identity() @@ -81,7 +71,12 @@ def get_timezone() -> Optional[ZoneInfo]: # Resolve outside the lock (config file I/O); first writer wins so concurrent resolvers of the # same identity converge on one ZoneInfo object. name = _resolve_timezone_name() - tz = _get_zoneinfo(name) + tz = None + if name: + try: + tz = ZoneInfo(name) + except Exception as exc: + logger.warning("Invalid timezone '%s': %s. Falling back to server local time.", name, exc) with _cache_lock: return _tz_cache.setdefault(cache_identity, (name, tz))[1] diff --git a/registration_lifecycle.py b/registration_lifecycle.py index 21601e144c..6724cc2fe9 100644 --- a/registration_lifecycle.py +++ b/registration_lifecycle.py @@ -50,10 +50,8 @@ class ReplacementCoordinator: with self._lock: yield - def acquire( - self, slot: Hashable, *, current: Any, previous: Any, restore: Callable[[Any], bool], - finalize: Callable[[], None] | None = None, - ) -> ReplacementLease: + def acquire(self, slot: Hashable, *, current: Any, previous: Any, restore: Callable[[Any], bool], + finalize: Callable[[], None] | None = None) -> ReplacementLease: """Attach a new live generation to the matching active predecessor.""" with self._lock: leases = self._active.setdefault(slot, []) @@ -63,7 +61,10 @@ class ReplacementCoordinator: return lease def dispose(self, lease: ReplacementLease) -> None: - """Remove *lease*, restoring the nearest still-live predecessor.""" + """Remove *lease*, restoring the nearest still-live predecessor. + + ``restore`` -> ``finalize`` -> slot pruning each run even when an earlier step raises. + """ with self._lock: if not lease.active: return @@ -75,25 +76,20 @@ class ReplacementCoordinator: try: try: if latest is lease: - replacement = lease.previous - predecessor = lease.predecessor - while predecessor is not None: - if predecessor.active: - replacement = predecessor.current - break - replacement = predecessor.previous - predecessor = predecessor.predecessor - lease.restore(replacement) + # Restore the nearest still-live predecessor, else the last dead generation's previous. + replacement, predecessor = lease.previous, lease.predecessor + while predecessor is not None and not predecessor.active: + replacement, predecessor = predecessor.previous, predecessor.predecessor + lease.restore(predecessor.current if predecessor is not None else replacement) finally: if lease.finalize is not None: lease.finalize() finally: - if leases: - live = [item for item in leases if item.active] - if live: - self._active[lease.slot] = live - else: - self._active.pop(lease.slot, None) + live = [item for item in leases if item.active] + if live: + self._active[lease.slot] = live + elif leases: + self._active.pop(lease.slot, None) replacement_coordinator = ReplacementCoordinator() diff --git a/toolset_distributions.py b/toolset_distributions.py index c20372524d..409d7ce818 100644 --- a/toolset_distributions.py +++ b/toolset_distributions.py @@ -15,29 +15,20 @@ def _dist(description: str, **toolsets: int) -> Dict[str, object]: DISTRIBUTIONS = { - "default": _dist("All available tools, all the time", - web=100, vision=100, image_gen=100, terminal=100, file=100, browser=100), - "image_gen": _dist("Heavy focus on image generation with vision and web support", - image_gen=90, vision=90, web=55, terminal=45), - "research": _dist("Web research with vision analysis and reasoning", - web=90, browser=70, vision=50, terminal=10), + "default": _dist("All available tools, all the time", web=100, vision=100, image_gen=100, terminal=100, file=100, browser=100), + "image_gen": _dist("Heavy focus on image generation with vision and web support", image_gen=90, vision=90, web=55, terminal=45), + "research": _dist("Web research with vision analysis and reasoning", web=90, browser=70, vision=50, terminal=10), "science": _dist("Scientific research with web, terminal, file, and browser capabilities", web=94, terminal=94, file=94, vision=65, browser=50, image_gen=15), - "development": _dist("Terminal, file tools, and reasoning with occasional web lookup", - terminal=80, file=80, web=30, vision=10), - "safe": _dist("All tools except terminal for safety", - web=80, browser=70, vision=60, image_gen=60), - "balanced": _dist("Equal probability of all toolsets", - web=50, vision=50, image_gen=50, terminal=50, file=50, browser=50), + "development": _dist("Terminal, file tools, and reasoning with occasional web lookup", terminal=80, file=80, web=30, vision=10), + "safe": _dist("All tools except terminal for safety", web=80, browser=70, vision=60, image_gen=60), + "balanced": _dist("Equal probability of all toolsets", web=50, vision=50, image_gen=50, terminal=50, file=50, browser=50), "minimal": _dist("Only web tools for basic research", web=100), "terminal_only": _dist("Terminal and file tools for code execution tasks", terminal=100, file=100), - "terminal_web": _dist("Terminal and file tools with web search for documentation lookup", - terminal=100, file=100, web=100), + "terminal_web": _dist("Terminal and file tools with web search for documentation lookup", terminal=100, file=100, web=100), "creative": _dist("Image generation and vision analysis focus", image_gen=90, vision=90, web=30), - "reasoning": _dist("Heavy research/reasoning distribution with minimal other tools", - web=90, file=60, terminal=20), - "browser_use": _dist("Full browser-based web interaction with search, vision, and page control", - browser=100, web=80, vision=70), + "reasoning": _dist("Heavy research/reasoning distribution with minimal other tools", web=90, file=60, terminal=20), + "browser_use": _dist("Full browser-based web interaction with search, vision, and page control", browser=100, web=80, vision=70), "browser_only": _dist("Only browser automation tools for pure web interaction tasks", browser=100), # browser-use-tasks.jsonl: the browser toolset includes web_search since Google blocks direct browser searches "browser_tasks": _dist( @@ -45,15 +36,11 @@ DISTRIBUTIONS = { browser=97, vision=12, terminal=15, ), # nous-terminal-tasks.jsonl - "terminal_tasks": _dist( - "Terminal-focused distribution with high terminal/file availability, occasional other tools", - terminal=97, file=97, web=97, browser=75, vision=50, image_gen=10, - ), + "terminal_tasks": _dist("Terminal-focused distribution with high terminal/file availability, occasional other tools", + terminal=97, file=97, web=97, browser=75, vision=50, image_gen=10), # mixed-browser-terminal-tasks.jsonl - "mixed_tasks": _dist( - "Mixed distribution with high browser, terminal, and file availability for complex tasks", - browser=92, terminal=92, file=92, web=35, vision=15, image_gen=15, - ), + "mixed_tasks": _dist("Mixed distribution with high browser, terminal, and file availability for complex tasks", + browser=92, terminal=92, file=92, web=35, vision=15, image_gen=15), } @@ -66,6 +53,10 @@ def list_distributions() -> Dict[str, Dict]: return DISTRIBUTIONS.copy() +def validate_distribution(distribution_name: str) -> bool: + return distribution_name in DISTRIBUTIONS + + def sample_toolsets_from_distribution(distribution_name: str) -> List[str]: """Sample toolset names, each included independently with its % probability. @@ -75,34 +66,25 @@ def sample_toolsets_from_distribution(distribution_name: str) -> List[str]: dist = get_distribution(distribution_name) if not dist: raise ValueError(f"Unknown distribution: {distribution_name}") - selected_toolsets = [] for toolset_name, probability in dist["toolsets"].items(): if not validate_toolset(toolset_name): print(f"āš ļø Warning: Toolset '{toolset_name}' in distribution '{distribution_name}' is not valid") - continue - if random.random() * 100 < probability: + elif random.random() * 100 < probability: selected_toolsets.append(toolset_name) - if not selected_toolsets and dist["toolsets"]: highest_prob_toolset = max(dist["toolsets"].items(), key=lambda x: x[1])[0] if validate_toolset(highest_prob_toolset): selected_toolsets.append(highest_prob_toolset) - return selected_toolsets -def validate_distribution(distribution_name: str) -> bool: - return distribution_name in DISTRIBUTIONS - - def print_distribution_info(distribution_name: str) -> None: """Print a distribution's description and toolset probabilities (highest first).""" dist = get_distribution(distribution_name) if not dist: print(f"āŒ Unknown distribution: {distribution_name}") return - print(f"\nšŸ“Š Distribution: {distribution_name}") print(f" Description: {dist['description']}") print(" Toolsets:")