Preserve macOS app permissions via dynamic launcher, .env workspace path, and session/audio tools
This commit is contained in:
+48
-6
@@ -95,16 +95,24 @@ def find_hermes_cli() -> str | None:
|
||||
return shutil.which("hermes")
|
||||
|
||||
|
||||
async def ensure_hermes_server(port: int = 8642) -> tuple[bool, str]:
|
||||
"""Ensure Hermes gateway server daemon is available on port."""
|
||||
async def check_hermes_server_active(port: int = 8642) -> tuple[bool, str]:
|
||||
"""Check if Hermes gateway server daemon is responding to health requests."""
|
||||
url = f"http://localhost:{port}/api/health"
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=2.0)) as session:
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1.5)) as session:
|
||||
async with session.get(url) as resp:
|
||||
if resp.status == 200:
|
||||
return True, f"Hermes server active on http://localhost:{port}"
|
||||
except Exception:
|
||||
pass
|
||||
return False, "Hermes server daemon not active"
|
||||
|
||||
|
||||
async def ensure_hermes_server(port: int = 8642) -> tuple[bool, str]:
|
||||
"""Ensure Hermes gateway server daemon or CLI binary is available."""
|
||||
active, msg = await check_hermes_server_active(port)
|
||||
if active:
|
||||
return True, msg
|
||||
|
||||
cli = find_hermes_cli()
|
||||
if not cli:
|
||||
@@ -146,6 +154,7 @@ class HermesLLM(FrameProcessor):
|
||||
self._cli_path = find_hermes_cli() or "hermes"
|
||||
self._use_server = use_server
|
||||
self._session_renamed = False
|
||||
self._http_session: aiohttp.ClientSession | None = None
|
||||
|
||||
# Keep the conversation lineage with the workspace. A single global
|
||||
# session file can make two voice-agent workspaces resume each other's
|
||||
@@ -157,6 +166,37 @@ class HermesLLM(FrameProcessor):
|
||||
if self._session_id:
|
||||
logger.info(f"Loaded existing Hermes session ID: {self._session_id}")
|
||||
|
||||
def reset_session(self):
|
||||
"""Reset active session so a fresh session starts on the next turn."""
|
||||
logger.info("Resetting active Hermes session state...")
|
||||
self._session_id = None
|
||||
self._session_renamed = False
|
||||
self._history.clear()
|
||||
try:
|
||||
if self._session_state_file.exists():
|
||||
self._session_state_file.unlink()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not remove session file during reset: {e}")
|
||||
|
||||
def _sync_disk_session(self):
|
||||
"""Sync in-memory session ID with disk file state prior to each turn."""
|
||||
disk_sid = self._load_session_id()
|
||||
if disk_sid != self._session_id:
|
||||
logger.info(f"Hermes session state updated from disk: {self._session_id} -> {disk_sid}")
|
||||
self._session_id = disk_sid
|
||||
self._session_renamed = False
|
||||
self._history.clear()
|
||||
|
||||
async def _get_http_session(self) -> aiohttp.ClientSession:
|
||||
if self._http_session is None or self._http_session.closed:
|
||||
self._http_session = aiohttp.ClientSession()
|
||||
return self._http_session
|
||||
|
||||
async def _close_http_session(self):
|
||||
if self._http_session and not self._http_session.closed:
|
||||
await self._http_session.close()
|
||||
self._http_session = None
|
||||
|
||||
def _load_session_id(self) -> str | None:
|
||||
if self._session_state_file.exists():
|
||||
try:
|
||||
@@ -219,6 +259,7 @@ class HermesLLM(FrameProcessor):
|
||||
logger.info(f"Hermes LLM engine initialized: {reason}")
|
||||
elif isinstance(frame, (EndFrame, CancelFrame)):
|
||||
await self._cancel_turn()
|
||||
await self._close_http_session()
|
||||
await self.push_frame(frame, direction)
|
||||
elif isinstance(frame, InterruptionFrame):
|
||||
await self._cancel_turn()
|
||||
@@ -289,6 +330,7 @@ class HermesLLM(FrameProcessor):
|
||||
|
||||
async def _run_turn(self, utterance: str):
|
||||
self._sync_disk_model()
|
||||
self._sync_disk_session()
|
||||
self._history.append({"role": "user", "content": utterance})
|
||||
|
||||
await self.push_frame(LLMFullResponseStartFrame())
|
||||
@@ -313,7 +355,7 @@ class HermesLLM(FrameProcessor):
|
||||
async def _run_turn_server(self, utterance: str, chunks: list[str]):
|
||||
"""Run turn via Hermes Server / Gateway HTTP API if available."""
|
||||
try:
|
||||
ok, _ = await ensure_hermes_server(self._port)
|
||||
ok, _ = await check_hermes_server_active(self._port)
|
||||
if not ok:
|
||||
raise RuntimeError("Hermes server daemon unavailable")
|
||||
|
||||
@@ -325,8 +367,8 @@ class HermesLLM(FrameProcessor):
|
||||
if self._model and self._model.lower() not in ("default", "none", ""):
|
||||
payload["model"] = self._model
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, json=payload) as resp:
|
||||
session = await self._get_http_session()
|
||||
async with session.post(url, json=payload) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
text_val = data.get("reply") or data.get("text") or data.get("content", "")
|
||||
|
||||
Reference in New Issue
Block a user