"""Active-persona selection — which script file Gemini loads as its system prompt. The operator can keep several persona variants in scripts/ (e.g. ``sanad_script.txt``, ``sanad_script_v1.txt``, ``sanad_script_v2.txt``) and pick which one is live. The selection is a single basename stored in ``data/active_persona.txt``; the DEFAULT (and reset target) is always the configured persona (``sanad_script.txt``). The Gemini child resolves this at session start, so a new selection takes effect on the next voice (re)connect. A missing/blank/stale pointer transparently falls back to the default, so this can never break the voice — worst case it loads ``sanad_script.txt``. """ from __future__ import annotations from pathlib import Path from Project.Sanad.config import DATA_DIR, SCRIPTS_DIR ACTIVE_PERSONA_FILE = DATA_DIR / "active_persona.txt" def default_persona_name() -> str: """The configured default persona filename (core.script_files.persona).""" try: from Project.Sanad.core.config_loader import section as _section name = (_section("core", "script_files") or {}).get("persona") return (name or "sanad_script.txt").strip() or "sanad_script.txt" except Exception: return "sanad_script.txt" def active_persona_name() -> str: """Selected persona basename — the chosen variant if set AND still exists, otherwise the default. Never raises.""" default = default_persona_name() try: sel = ACTIVE_PERSONA_FILE.read_text(encoding="utf-8").strip() except Exception: sel = "" if sel: cand = SCRIPTS_DIR / Path(sel).name # basename only — no traversal if cand.is_file(): return cand.name return default def active_persona_path() -> Path: """Full path to the persona script Gemini should load right now.""" return SCRIPTS_DIR / active_persona_name() def set_active_persona(name: str | None) -> str: """Persist the selected persona basename. Passing None/"" or the default name clears the pointer (revert to default). Returns the effective active name. Raises FileNotFoundError if a non-default name doesn't exist.""" nm = (Path(str(name)).name if name else "").strip() default = default_persona_name() if not nm or nm == default: try: ACTIVE_PERSONA_FILE.unlink() except FileNotFoundError: pass except Exception: pass return default if not (SCRIPTS_DIR / nm).is_file(): raise FileNotFoundError(nm) DATA_DIR.mkdir(parents=True, exist_ok=True) ACTIVE_PERSONA_FILE.write_text(nm, encoding="utf-8") return nm