diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index aab27242..d11b5b67 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -1065,8 +1065,15 @@ def _run_gateway( def _pick_heartbeat_target() -> tuple[str, str]: """Pick a routable channel/chat target for heartbeat-triggered messages.""" enabled = set(channels.enabled_channels) + + from nanobot.webui.sidebar_state import read_webui_sidebar_state + sidebar_state = read_webui_sidebar_state() + archived_keys = set(sidebar_state.get("archived_keys", [])) + for item in session_manager.list_sessions(): key = item.get("key") or "" + if key in archived_keys: + continue if ":" not in key: continue channel, chat_id = key.split(":", 1) diff --git a/nanobot/session/manager.py b/nanobot/session/manager.py index c951c651..da62f0f0 100644 --- a/nanobot/session/manager.py +++ b/nanobot/session/manager.py @@ -843,11 +843,13 @@ class SessionManager: if not fallback_preview and item.get("role") == "assistant": fallback_preview = text preview = preview or fallback_preview + from datetime import datetime + fallback_time = datetime.fromtimestamp(path.stat().st_mtime).isoformat() sessions.append( { "key": key, - "created_at": data.get("created_at"), - "updated_at": data.get("updated_at"), + "created_at": data.get("created_at") or fallback_time, + "updated_at": data.get("updated_at") or fallback_time, "title": title, "preview": preview, "path": str(path), diff --git a/nanobot/webui/session_list_index.py b/nanobot/webui/session_list_index.py index aaadc97a..fdd64575 100644 --- a/nanobot/webui/session_list_index.py +++ b/nanobot/webui/session_list_index.py @@ -269,13 +269,20 @@ def _scan_session_row(session_manager: SessionManager, path: Path) -> dict[str, if not fallback_preview and item.get("role") == "assistant": fallback_preview = text signature = _file_signature(path) + created_at_s = data.get("created_at") + updated_at_s = data.get("updated_at") + if not created_at_s or not updated_at_s: + from datetime import datetime + fallback_time = datetime.fromtimestamp(signature["mtime_ns"] / 1e9).isoformat() + created_at_s = created_at_s or fallback_time + updated_at_s = updated_at_s or fallback_time key = data.get("key") or fallback_key activity_signature = _webui_activity_signature(key) activity_updated_at = _webui_activity_updated_at(activity_signature) return { "key": key, - "created_at": data.get("created_at"), - "updated_at": _latest_updated_at(data.get("updated_at"), activity_updated_at), + "created_at": created_at_s, + "updated_at": _latest_updated_at(updated_at_s, activity_updated_at), "title": _metadata_title(data.get("metadata", {})), "preview": preview or fallback_preview, "file": path.name, diff --git a/tests/webui/test_session_list_index.py b/tests/webui/test_session_list_index.py index cc10f8c1..e68f9eeb 100644 --- a/tests/webui/test_session_list_index.py +++ b/tests/webui/test_session_list_index.py @@ -171,3 +171,40 @@ def test_webui_session_list_rescans_when_transcript_changes( def list_webui_sessions(manager: SessionManager) -> list[dict]: return session_list_index.list_webui_sessions(manager) + + +def test_webui_session_list_fallback_time_when_missing(tmp_path: Path) -> None: + manager = SessionManager(tmp_path) + path = manager._get_session_path("websocket:missing-time") + path.write_text( + '{"_type": "metadata", "key": "websocket:missing-time"}\n' + '{"_type": "message", "role": "user", "content": "hello"}\n', + encoding="utf-8", + ) + + rows = list_webui_sessions(manager) + assert len(rows) == 1 + assert rows[0]["key"] == "websocket:missing-time" + assert rows[0]["created_at"] is not None + assert rows[0]["updated_at"] is not None + datetime.fromisoformat(rows[0]["created_at"]) + datetime.fromisoformat(rows[0]["updated_at"]) + + +def test_session_manager_list_sessions_fallback_time_when_missing(tmp_path: Path) -> None: + manager = SessionManager(tmp_path) + path = manager._get_session_path("websocket:missing-time2") + path.write_text( + '{"_type": "metadata", "key": "websocket:missing-time2"}\n' + '{"_type": "message", "role": "user", "content": "hello"}\n', + encoding="utf-8", + ) + + sessions = manager.list_sessions() + assert len(sessions) == 1 + assert sessions[0]["key"] == "websocket:missing-time2" + assert sessions[0]["created_at"] is not None + assert sessions[0]["updated_at"] is not None + datetime.fromisoformat(sessions[0]["created_at"]) + datetime.fromisoformat(sessions[0]["updated_at"]) +