Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions api/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ def get_logs():
try:
bubble = json.loads(row["value"])
chat_map.setdefault(chat_id, []).append(bubble)
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to decode bubble row %s: %s",
row["key"],
e,
)

for chat_id, bubbles in chat_map.items():
bubbles = [b for b in bubbles if isinstance(b, dict)]
Expand Down Expand Up @@ -90,8 +94,12 @@ def get_logs():
with open(wj_path, "r", encoding="utf-8") as f:
wd = json.load(f)
workspace_folder = wd.get("folder")
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to read workspace.json for %s: %s",
name,
e,
)

try:
# closing() guarantees .close() on scope exit (issue #17).
Expand Down Expand Up @@ -130,10 +138,18 @@ def get_logs():
"type": "composer",
"messageCount": len(c.get("conversation") or []),
})
except Exception:
pass
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to read logs from workspace %s: %s",
name,
e,
)
except Exception as e:
_logger.warning(
"Failed to iterate workspaces under %s: %s",
workspace_path,
e,
)

logs.sort(key=lambda log: log.get("timestamp") or 0, reverse=True)
return jsonify({"logs": logs})
Expand Down
63 changes: 48 additions & 15 deletions api/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,18 @@ def search():
fn = parts[-1] if parts else None
if fn:
ws_id_to_name[name] = _url_unquote(fn)
except Exception:
pass
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to read workspace.json for %s: %s",
name,
e,
)
except Exception as e:
_logger.warning(
"Failed to list workspace entries under %s: %s",
workspace_path,
e,
)

# Build composer → workspace mapping
composer_id_to_ws = {}
Expand All @@ -139,8 +147,12 @@ def search():
cid = c.get("composerId") if isinstance(c, dict) else None
if cid:
composer_id_to_ws[cid] = entry["name"]
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to load composer mapping from workspace %s: %s",
entry["name"],
e,
)

# Load bubble text for searching
bubble_map = {}
Expand Down Expand Up @@ -261,8 +273,12 @@ def search():
"matchingText": matching_text,
"type": "composer",
})
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to process Composer from composerData:%s during search: %s",
composer_id,
e,
)

except Exception:
_logger.exception("Error searching global storage")
Expand All @@ -288,8 +304,12 @@ def search():
with open(wj_path, "r", encoding="utf-8") as f:
wd = json.load(f)
workspace_folder = wd.get("folder")
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to read workspace.json for %s: %s",
name,
e,
)
workspace_name = _workspace_display_name_from_folder(workspace_folder, fallback=name)

# try/finally guarantees .close() on every exit path (issue #17).
Expand Down Expand Up @@ -362,13 +382,21 @@ def search():
"type": "chat",
})

except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to search legacy workspace %s: %s",
name,
e,
)
finally:
if conn is not None:
conn.close()
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to iterate legacy workspaces under %s: %s",
workspace_path,
e,
)

# ---------------------------------------------------------------
# Search Cursor CLI sessions (only for type=all)
Expand All @@ -386,7 +414,12 @@ def search():

try:
messages = traverse_blobs(session["db_path"])
except Exception:
except Exception as e:
_logger.warning(
"Failed to traverse CLI session blobs for %s: %s",
session_id,
e,
)
continue

bubbles = messages_to_bubbles(messages, created_ms)
Expand Down
7 changes: 6 additions & 1 deletion api/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ def get_workspace(workspace_id):
inferred = _infer_workspace_name_from_context(workspace_path, workspace_id)
if inferred:
workspace_name = inferred
except Exception:
except Exception as e:
_logger.warning(
"Failed to read workspace.json for %s: %s",
workspace_id,
e,
)
inferred = _infer_workspace_name_from_context(workspace_path, workspace_id)
if inferred:
workspace_name = inferred
Expand Down
2 changes: 1 addition & 1 deletion requirements-lock.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Lock is generated on Linux (CI / update-lock.yml). Windows-only transitives (e.g.
# colorama via click) are omitted — pip still installs them on Windows when needed.
blinker==1.9.0 # via flask
click==8.4.0 # via flask
click==8.4.1 # via flask
defusedxml==0.7.1 # via fpdf2
flask==3.1.3 # via -r requirements.txt
fonttools==4.63.0 # via fpdf2
Expand Down
60 changes: 51 additions & 9 deletions services/workspace_listing.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

import json
import logging
import os
import sqlite3
from datetime import datetime, timezone

_logger = logging.getLogger(__name__)

from utils.cli_chat_reader import list_cli_projects
from utils.exclusion_rules import build_searchable_text, is_excluded_by_rules
from utils.path_helpers import (
Expand All @@ -14,6 +17,7 @@
)
from utils.workspace_descriptor import read_json_file
from utils.workspace_path import get_cli_chats_path
from models import Composer, SchemaError
from services.workspace_db import (
_build_composer_id_to_workspace_id,
_collect_invalid_workspace_ids,
Expand Down Expand Up @@ -72,7 +76,32 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
for row in composer_rows:
cid = row["key"].split(":")[1]
try:
cd = json.loads(row["value"])
parsed = json.loads(row["value"])
except (json.JSONDecodeError, TypeError, ValueError) as e:
_logger.warning(
"Failed to decode Composer from composerData:%s: %s",
cid,
e,
)
continue
if not isinstance(parsed, dict):
_logger.warning(
"Failed to parse Composer from composerData:%s: expected object, got %s",
cid,
type(parsed).__name__,
)
continue
try:
composer = Composer.from_dict(parsed, composer_id=cid)
except SchemaError as e:
_logger.warning(
"Failed to parse Composer from composerData:%s: %s",
cid,
e,
)
continue
cd = composer.raw
try:
pid = _determine_project_for_conversation(
cd, cid, project_layouts_map,
project_name_map, workspace_path_map,
Expand All @@ -98,10 +127,14 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
"lastUpdatedAt": to_epoch_ms(cd.get("lastUpdatedAt")) or to_epoch_ms(cd.get("createdAt")) or 0,
"createdAt": to_epoch_ms(cd.get("createdAt")) or 0,
})
except Exception:
pass
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to process Composer from composerData:%s: %s",
cid,
e,
)
except Exception as e:
_logger.error("Failed to load composer rows from global storage: %s", e)
Comment thread
bradjin8 marked this conversation as resolved.
Outdated

# Group workspace entries by normalized folder path
folder_to_entries: dict[str, list] = {}
Expand All @@ -114,8 +147,12 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
first_folder = folders[0] if folders else None
if first_folder:
norm_folder = normalize_file_path(first_folder)
except Exception:
pass
except Exception as e:
_logger.warning(
"Failed to read workspace.json for %s: %s",
entry["name"],
e,
)
if not norm_folder:
norm_folder = entry["name"] # fallback to workspace ID
entry_folder_map[entry["name"]] = norm_folder
Expand All @@ -139,7 +176,12 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
for e in group
if os.path.isfile(os.path.join(workspace_path, e["name"], "state.vscdb"))
)
except Exception:
except Exception as e:
_logger.warning(
"Failed to resolve mtime for workspace folder %s: %s",
norm_folder,
e,
)
mtime = 0

workspace_name = _get_workspace_display_name(workspace_path, primary["name"])
Expand Down Expand Up @@ -238,7 +280,7 @@ def _safe_fetchall(query: str, params: tuple = ()) -> list:
"source": "cli",
})
except Exception as e:
print(f"Failed to load CLI projects: {e}")
_logger.warning("Failed to load CLI projects: %s", e)

projects.sort(key=lambda p: p["lastModified"], reverse=True)
return projects
Loading
Loading