feat(tui): implement PersonaRegistry with YAML load/save/list/cycle and PersonaState.cycle_persona() #10603
@@ -160,6 +160,11 @@ class PersonaRegistry:
|
||||
lock.close()
|
||||
|
||||
def export_persona(self, name: str, output_path: Path) -> Path:
|
||||
"""Export a persona to a YAML file at the given path.
|
||||
|
||||
Accepts both absolute and relative output paths. Raises ValueError
|
||||
if the persona does not exist in the registry.
|
||||
"""
|
||||
persona = self.get(name)
|
||||
if persona is None:
|
||||
raise ValueError(f"Persona not found: {name}")
|
||||
@@ -169,6 +174,11 @@ class PersonaRegistry:
|
||||
return safe_output
|
||||
|
||||
def import_persona(self, input_path: Path) -> Persona:
|
||||
"""Import a persona from a YAML file at the given path.
|
||||
|
||||
Accepts both absolute and relative input paths. Raises ValueError
|
||||
if the file does not contain a valid persona dict.
|
||||
"""
|
||||
safe_input = self.resolve_import_path(input_path)
|
||||
raw = yaml.safe_load(safe_input.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(raw, dict):
|
||||
@@ -178,6 +188,11 @@ class PersonaRegistry:
|
||||
return persona
|
||||
|
||||
def load_state(self) -> dict[str, Any]:
|
||||
"""Load the TUI state dict from the state YAML file.
|
||||
|
||||
Returns an empty dict if the file does not exist or contains
|
||||
non-dict content.
|
||||
"""
|
||||
if not self.state_path.exists():
|
||||
return {}
|
||||
raw = yaml.safe_load(self.state_path.read_text(encoding="utf-8")) or {}
|
||||
@@ -186,15 +201,21 @@ class PersonaRegistry:
|
||||
return dict(raw)
|
||||
|
||||
def save_state(self, state: dict[str, Any]) -> None:
|
||||
"""Persist the TUI state dict to the state YAML file atomically."""
|
||||
self.ensure_dirs()
|
||||
self._atomic_write_yaml(self.state_path, state)
|
||||
|
||||
def get_last_persona(self) -> str | None:
|
||||
"""Return the name of the last active persona, or None if unset."""
|
||||
state = self.load_state()
|
||||
value = state.get("last_persona")
|
||||
return value if isinstance(value, str) and value else None
|
||||
|
||||
def set_last_persona(self, name: str) -> None:
|
||||
"""Persist the given persona name as the last active persona.
|
||||
|
||||
Uses an exclusive file lock to prevent concurrent write conflicts.
|
||||
"""
|
||||
lock = self._lock_file(self.state_lock_path)
|
||||
try:
|
||||
state = self.load_state()
|
||||
|
||||
@@ -21,11 +21,20 @@ class PersonaState:
|
||||
|
|
||||
return self.registry.get_last_persona() or default.name
|
||||
|
||||
def active_name(self, session_id: str) -> str:
|
||||
"""Return the active persona name for the given session.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
Initialises the session to the default persona on first access.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
"""
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
if session_id not in self.active_by_session:
|
||||
self.active_by_session[session_id] = self._resolve_default_name()
|
||||
return self.active_by_session[session_id]
|
||||
|
||||
def active_persona(self, session_id: str) -> Persona:
|
||||
"""Return the active Persona object for the given session.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
Falls back to the registry default if the stored name is no longer
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
present in the registry.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
"""
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
name = self.active_name(session_id)
|
||||
persona = self.registry.get(name)
|
||||
if persona is None:
|
||||
@@ -34,6 +43,11 @@ class PersonaState:
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
return persona
|
||||
|
||||
def set_active_persona(self, session_id: str, persona_name: str) -> Persona:
|
||||
"""Set the active persona for the given session and persist the choice.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
Raises ValueError if the persona name is not found in the registry.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
Initialises the session preset to "default" if not already set.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
"""
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
persona = self.registry.get(persona_name)
|
||||
if persona is None:
|
||||
raise ValueError(f"Unknown persona: {persona_name}")
|
||||
@@ -44,6 +58,10 @@ class PersonaState:
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
return persona
|
||||
|
||||
def current_preset(self, session_id: str) -> str:
|
||||
"""Return the current argument preset name for the given session.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
Defaults to "default" on first access.
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
"""
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
if session_id not in self.preset_by_session:
|
||||
self.preset_by_session[session_id] = "default"
|
||||
return self.preset_by_session[session_id]
|
||||
|
||||
|
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
HAL9001
commented
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority. Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
|
||||
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.
Suggestion: cycle_persona() calls self.registry.list_personas() on every Tab press. Consider caching. Also [p.name] plus .index() is O(n); dict/set would be O(1). Low priority.