Preserve macOS app permissions via dynamic launcher, .env workspace path, and session/audio tools

This commit is contained in:
Adolfo Reyna
2026-08-11 21:15:37 -04:00
parent 583131221c
commit b5034b4b16
17 changed files with 1888 additions and 68 deletions
+63 -2
View File
@@ -25,18 +25,21 @@ _sse_clients: Set[asyncio.Queue] = set()
_workspace_dir: Path = Path(__file__).parent.resolve()
_model_manager = None
_voice_manager = None
_audio_controller = None
_input_callback: Callable = None
_recent_events: list = []
_MAX_HISTORY_EVENTS = 200
def set_managers(workspace: Path, model_mgr=None, voice_mgr=None, input_callback=None):
global _workspace_dir, _model_manager, _voice_manager, _input_callback
def set_managers(workspace: Path, model_mgr=None, voice_mgr=None, input_callback=None, audio_controller=None):
global _workspace_dir, _model_manager, _voice_manager, _input_callback, _audio_controller
_workspace_dir = Path(workspace)
_model_manager = model_mgr
_voice_manager = voice_mgr
if input_callback is not None:
_input_callback = input_callback
if audio_controller is not None:
_audio_controller = audio_controller
def broadcast_event(event_type: str, payload: dict):
@@ -185,6 +188,43 @@ async def handle_set_voice(request):
return web.json_response({"error": str(e)}, status=500)
async def handle_reset_session(request):
try:
from bin.session_tool import reset_session
ok, msg = reset_session(_workspace_dir)
if ok:
broadcast_event("session_reset", {"message": msg})
return web.json_response({"success": True, "message": msg})
return web.json_response({"error": msg}, status=500)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
async def handle_get_audio_devices(request):
if not _audio_controller:
return web.json_response({"error": "Audio controller not active"}, status=503)
return web.json_response({"devices": _audio_controller.available_devices()})
async def handle_set_audio_device(request):
try:
if not _audio_controller:
return web.json_response({"error": "Audio controller not active"}, status=503)
body = await request.json()
direction, device = body.get("direction"), body.get("device")
if direction not in {"input", "output"}:
return web.json_response({"error": "direction must be input or output"}, status=400)
result = await _audio_controller.set_runtime_device(direction, device)
broadcast_event("audio_device_selected", result)
return web.json_response({"success": True, **result})
except (ValueError, RuntimeError) as exc:
logger.warning(f"Runtime audio-device selection failed: {exc}")
return web.json_response({"error": str(exc)}, status=400)
except Exception as exc:
logger.exception("Runtime audio-device selection failed")
return web.json_response({"error": str(exc)}, status=500)
async def handle_show_file_api(request):
try:
body = await request.json()
@@ -249,9 +289,12 @@ async def start_server(workspace: Path, host: str = "127.0.0.1", port: int = 888
app.router.add_get("/api/models", handle_get_models)
app.router.add_post("/api/model", handle_set_model)
app.router.add_post("/api/voice", handle_set_voice)
app.router.add_get("/api/audio-devices", handle_get_audio_devices)
app.router.add_post("/api/audio-device", handle_set_audio_device)
app.router.add_post("/api/show_file", handle_show_file_api)
app.router.add_post("/api/open_browser", handle_open_browser_api)
app.router.add_post("/api/send", handle_send_message_api)
app.router.add_post("/api/session/reset", handle_reset_session)
runner = web.AppRunner(app)
await runner.setup()
@@ -692,6 +735,9 @@ HTML_INDEX = """<!DOCTYPE html>
<div class="pill" id="voicePill" onclick="openVoiceModal()">
Voice: <strong id="voiceName">Loading...</strong>
</div>
<div class="pill" id="resetPill" onclick="resetSession()" title="Start a fresh conversation session">
🔄 <strong>Reset Session</strong>
</div>
</div>
</header>
@@ -843,6 +889,21 @@ HTML_INDEX = """<!DOCTYPE html>
.catch(err => console.error('Failed to send message:', err));
}
async function resetSession() {
if (!confirm('Start a fresh Hermes conversation session?')) return;
try {
const res = await fetch('/api/session/reset', { method: 'POST' });
const data = await res.json();
if (data.success) {
appendToolStep('System', data.message || 'Session reset.');
} else {
alert('Failed to reset session: ' + (data.error || 'Unknown error'));
}
} catch (err) {
alert('Error resetting session: ' + err.message);
}
}
function handleKeyDown(e) {
if (e.key === 'Enter') {
sendMessage();