"""Tool: read/update entries inside orch_kv (DB-backed, replaces .a0/*.json).""" from __future__ import annotations import json import os from helpers.tool import Tool, Response from usr.plugins.a0_software_orchestrator.helpers.db_state_store import ( get_project_id, get_kv, set_kv, delete_kv, list_kv_keys, set_status as db_set_status, get_status as db_get_status, ) from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError def _resolve_name(project_name: str = "", project_root: str = "") -> str: if project_name: return project_name.strip() if project_root: return os.path.basename(project_root.rstrip("/")) return os.path.basename(os.getcwd()) class OrchestratorState(Tool): """Read or update KV-entries for a project in `orch_kv` (patterns.db). Args (kwargs): project_name (str): Projektname (bevorzugt). project_root (str): Legacy – wird zu basename() aufgelöst. action (str): "read", "update", "delete", "list_keys" (default "read"). key (str): Schlüssel-Name (z.B. "phase", "orchestrator_mode", "task_graph", "tool_capabilities"). Bei "read" ohne key: gibt alle Keys+Values als Dict zurück. value (str|dict): JSON-string oder dict für action=update. """ async def execute( self, project_name: str = "", project_root: str = "", action: str = "read", key: str = "", value: str = "", **kwargs, ): name = _resolve_name(project_name, project_root) try: pid = get_project_id(name) if pid is None: raise LookupError(f"project '{name}' is not registered") except (ValueError, LookupError) as e: return Response(message=f"ERROR: {e}", break_loop=False) except PluginDBError as e: return Response(message=f"Database error: {e}", break_loop=False) if action == "set_phase": action = "update" key = "phase" value = kwargs.get("phase", value) if action == "get_phase": action = "read" key = "phase" if action == "set_status": content = kwargs.get("status", value) if not content: return Response(message="ERROR: 'status' content required for action=set_status", break_loop=False) db_set_status(pid, str(content)) return Response(message=f"Updated status for project '{name}'.", break_loop=False) if action == "get_status": content = db_get_status(pid) return Response(message=content or f"No status set for project '{name}'.", break_loop=False) if action == "list_keys": keys = list_kv_keys(pid) return Response( message=f"Keys for project '{name}': {', '.join(keys) if keys else '(none)'}", break_loop=False, ) if action == "read": if not key: # Alle Keys als Dict zurückgeben keys = list_kv_keys(pid) data = {k: get_kv(pid, k) for k in keys} return Response( message=json.dumps(data, indent=2, ensure_ascii=False, default=str), break_loop=False, ) v = get_kv(pid, key, default=None) if v is None: return Response( message=f"Key '{key}' not set for project '{name}'.", break_loop=False, ) return Response( message=json.dumps(v, indent=2, ensure_ascii=False, default=str), break_loop=False, ) if action == "update": if not key: return Response( message="ERROR: 'key' required for action=update", break_loop=False, ) # value kann dict (direkt) oder str (JSON-encoded) sein if isinstance(value, str): try: parsed = json.loads(value) if value.strip() else None except json.JSONDecodeError: # Nicht-JSON-String → als reiner String speichern parsed = value else: parsed = value set_kv(pid, key, parsed) return Response( message=f"Updated key '{key}' for project '{name}'.", break_loop=False, ) if action == "delete": if not key: return Response( message="ERROR: 'key' required for action=delete", break_loop=False, ) ok = delete_kv(pid, key) return Response( message=( f"Deleted key '{key}' for project '{name}'." if ok else f"Key '{key}' did not exist for project '{name}'." ), break_loop=False, ) return Response( message=f"Unknown action {action!r}. Supported: read, update, delete, list_keys, set_phase, get_phase, set_status, get_status", break_loop=False, )