feat(desktop): session hygiene, archive, media streaming + connecting overlay (#37099)

* feat(desktop): session hygiene, archive, media streaming + connecting overlay

Address a batch of desktop feedback:

- Stop leaking empty "Untitled" sessions: the TUI gateway pre-created a DB
  row on every session.create (i.e. every launch/draft). Persist the row
  lazily on first prompt instead, and hide message-less rows in the sidebar.
- Archive/hide sessions: new `archived` column + set_session_archived, web
  API (`?archived=` + PATCH archived), Ctrl/⌘-click and a context-menu item
  in the sidebar, and an "Archived Chats" settings panel to restore/delete.
- Videos load via a streaming `hermes-media://` protocol instead of capped,
  in-memory data URLs (16 MB limit) — bypasses the cap and supports seeking.
- Background-process completions route to the session that launched them:
  the completion event now carries session_key and each poller only consumes
  its own.
- Sidebar: "Group by workspace" toggle is always visible; each workspace
  group gets a "+" to start a session in that directory; "New agent"/"Agents"
  relabeled to "New session"/"Sessions".
- New gateway connecting overlay (ascii decode → fade out) replacing the bare
  skeleton/"starting gateway" state.

* fix(desktop): bail connecting overlay on boot error

The shownRef latch kept the connecting overlay mounted behind
BootFailureOverlay after a hard boot failure. Return null on boot.error
so the failure recovery surface fully owns the screen.

* fix(desktop): address Copilot review

- /api/sessions: validate `archived` (400 on unknown) and return `archived`
  as a JSON boolean instead of SQLite's 0/1.
- PATCH /api/sessions/{id}: 400 (not a misleading 404) when the body has no
  updatable fields; stop conflating a no-op with "not found".
- hermes-media protocol: drop `bypassCSP` — streaming only needs
  secure/standard/stream/supportFetchAPI.
- Sidebar workspace header: split the toggle and the "+" into sibling buttons
  so we no longer nest interactive elements inside a <button>.

* fix(desktop): address Copilot re-review

- hermes-media protocol: restrict streaming to an audio/video extension
  allowlist (415 otherwise) so it can't be used to read arbitrary local files.
- Connecting overlay: use z-[1200] instead of the non-standard z-1200 utility.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
brooklyn!
2026-06-01 20:41:34 -05:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent ddc22866a3
commit 85b65e29f0
26 changed files with 1000 additions and 77 deletions
+67
View File
@@ -884,6 +884,73 @@ def test_session_title_queues_when_db_row_not_ready(monkeypatch):
server._sessions.pop("sid", None)
def test_notification_event_routing_by_session_key(monkeypatch):
"""Background-process events surface only in the session that owns them."""
mine = _session(session_key="mine")
other = _session(session_key="other")
monkeypatch.setattr(server, "_sessions", {"a": mine, "b": other})
# My own event → handle it.
assert server._notification_event_belongs_elsewhere(mine, {"session_key": "mine"}) is False
# Global/system event with no owner → handle it.
assert server._notification_event_belongs_elsewhere(mine, {"session_key": ""}) is False
assert server._notification_event_belongs_elsewhere(mine, {}) is False
# Owned by another *live* session → defer to that session's poller.
assert server._notification_event_belongs_elsewhere(mine, {"session_key": "other"}) is True
# Owner is gone (not in _sessions) → handle as fallback so it isn't lost.
assert server._notification_event_belongs_elsewhere(mine, {"session_key": "ghost"}) is False
def test_session_create_does_not_persist_empty_row(monkeypatch):
"""session.create must NOT eagerly write a DB row.
Every TUI/desktop launch opens a session here just to paint the composer;
eagerly creating a row left an empty "Untitled" session behind for every
launch the user never typed into. The row is created lazily on first prompt.
"""
created = []
class _FakeDB:
def create_session(self, *args, **kwargs):
created.append((args, kwargs))
monkeypatch.setattr(server, "_get_db", lambda: _FakeDB())
monkeypatch.setattr(server, "_start_agent_build", lambda *a, **k: None)
monkeypatch.setattr(
server.threading,
"Timer",
lambda *a, **k: types.SimpleNamespace(daemon=False, start=lambda: None),
)
resp = server.handle_request(
{"id": "1", "method": "session.create", "params": {"cols": 80}}
)
sid = resp["result"]["session_id"]
try:
assert resp["result"]["stored_session_id"]
assert created == [], "session.create should not persist an empty DB row"
finally:
server._sessions.pop(sid, None)
def test_ensure_session_db_row_persists_with_cwd(monkeypatch, tmp_path):
"""First prompt persists the row (INSERT OR IGNORE) capturing cwd up front."""
created = []
class _FakeDB:
def create_session(self, key, source=None, model=None, cwd=None):
created.append({"key": key, "source": source, "model": model, "cwd": cwd})
monkeypatch.setattr(server, "_get_db", lambda: _FakeDB())
monkeypatch.setattr(server, "_resolve_model", lambda: "test-model")
server._ensure_session_db_row({"session_key": "k1", "cwd": str(tmp_path)})
assert created == [
{"key": "k1", "source": "tui", "model": "test-model", "cwd": str(tmp_path)}
]
def test_session_title_clears_pending_after_persist(monkeypatch):
class _FakeDB:
def __init__(self):