From 7fbe9b79ab1d3a93689fd8baf09896c5aff233e4 Mon Sep 17 00:00:00 2001 From: brooklyn! Date: Mon, 1 Jun 2026 00:01:28 -0500 Subject: [PATCH] fix(desktop): add missing PATCH /api/sessions/{id} so rename works (#36249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop rename dialog sent PATCH /api/sessions/{id}, but the backend only defined GET and DELETE for that path — FastAPI returned 405 Method Not Allowed, surfaced to the user as "Rename failed". Add the PATCH route backed by SessionDB.set_session_title (handles sanitization, uniqueness, and clearing the title when empty). Also fix a misleading notification: any 405 was summarized as an unrelated "does not support that audio endpoint" message. Make it a generic 405 hint. --- apps/desktop/src/store/notifications.ts | 3 +- hermes_cli/web_server.py | 25 ++++++++++++++ tests/hermes_cli/test_web_server.py | 45 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/store/notifications.ts b/apps/desktop/src/store/notifications.ts index 2c091b6b9..d99887f27 100644 --- a/apps/desktop/src/store/notifications.ts +++ b/apps/desktop/src/store/notifications.ts @@ -68,7 +68,8 @@ const ERROR_SUMMARIES: { test: (msg: string) => boolean; summarize: (msg: string }, { test: msg => /method not allowed/i.test(msg), - summarize: () => 'The desktop backend does not support that audio endpoint yet. Restart Hermes Desktop.' + summarize: () => + 'The desktop backend rejected that request (405 Method Not Allowed). Try restarting Hermes Desktop.' }, { test: msg => /microphone permission/i.test(msg), diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 666d3f9f2..398b0f5de 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -3657,6 +3657,31 @@ async def delete_session_endpoint(session_id: str): db.close() +class SessionRename(BaseModel): + title: Optional[str] = None + + +@app.patch("/api/sessions/{session_id}") +async def rename_session_endpoint(session_id: str, body: SessionRename): + """Rename a session (or clear its title when ``title`` is empty/null).""" + from hermes_state import SessionDB + db = SessionDB() + try: + sid = db.resolve_session_id(session_id) + if not sid: + raise HTTPException(status_code=404, detail="Session not found") + try: + updated = db.set_session_title(sid, body.title or "") + except ValueError as e: + # Title too long, invalid characters, or already in use. + raise HTTPException(status_code=400, detail=str(e)) + if not updated: + raise HTTPException(status_code=404, detail="Session not found") + return {"ok": True, "title": db.get_session_title(sid) or ""} + finally: + db.close() + + # --------------------------------------------------------------------------- # Log viewer endpoint # --------------------------------------------------------------------------- diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 86d9e99c7..9f6efd25d 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -205,6 +205,51 @@ class TestWebServerEndpoints: assert captured["list"] == 3 assert captured["count"] == 3 + def test_rename_session_updates_title(self): + """PATCH /api/sessions/{id} renames a session (regression: the route + was missing entirely, so the desktop rename dialog got a 405).""" + from hermes_state import SessionDB + + db = SessionDB() + try: + db.create_session(session_id="rename-me", source="cli") + finally: + db.close() + + resp = self.client.patch("/api/sessions/rename-me", json={"title": "My Chat"}) + assert resp.status_code == 200 + assert resp.json() == {"ok": True, "title": "My Chat"} + + db = SessionDB() + try: + assert db.get_session_title("rename-me") == "My Chat" + finally: + db.close() + + def test_rename_session_clears_title_when_empty(self): + from hermes_state import SessionDB + + db = SessionDB() + try: + db.create_session(session_id="clear-me", source="cli") + db.set_session_title("clear-me", "Has A Title") + finally: + db.close() + + resp = self.client.patch("/api/sessions/clear-me", json={"title": ""}) + assert resp.status_code == 200 + assert resp.json() == {"ok": True, "title": ""} + + db = SessionDB() + try: + assert db.get_session_title("clear-me") is None + finally: + db.close() + + def test_rename_session_not_found(self): + resp = self.client.patch("/api/sessions/does-not-exist", json={"title": "x"}) + assert resp.status_code == 404 + def test_audio_transcription_endpoint(self, monkeypatch): import tools.transcription_tools as transcription_tools