feat: harden video analysis and runtime resilience

This commit is contained in:
somew1nd committed 2026-08-25 20:58:25 -07:00
1 parent 5ada543a09
commit bb16991f2c
55 files changed
+5023 -437

No files matched your search

@@ -88,6 +88,43 @@ class SessionRepository:
except Exception:
return None
def get_latest_video_recordings_map(self) -> dict[str, dict[str, Any]]:
"""Return the newest recording row for every session in one query.
The task-list endpoint renders every session at once. Calling
``get_video_recording_for_session`` in that loop used to open a new
SQLite connection for every row, which made a refresh progressively
slower as history grew.
"""
try:
with db_session(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='video_recordings'"
)
if cursor.fetchone() is None:
return {}
rows = cursor.execute(
"SELECT * FROM video_recordings WHERE session_id IS NOT NULL "
"ORDER BY start_time DESC"
).fetchall()
latest: dict[str, dict[str, Any]] = {}
for row in rows:
recording = dict(row)
session_id = str(recording.get("session_id") or "")
if not session_id or session_id in latest:
continue
if "status" not in recording:
recording["status"] = (
"ready" if recording.get("end_time") is not None else "recording"
)
recording.setdefault("error", None)
latest[session_id] = recording
return latest
except Exception:
return {}
def mark_recording_failed_if_pending(self, session_id: str, error: str) -> bool:
"""Close a recording lifecycle when its worker exits before finalization."""
try:
@@ -114,6 +151,40 @@ class SessionRepository:
rows = cursor.fetchall()
return [r[0] for r in rows if r and r[0]]
def get_llm_traces_for_profiles_map(
self, session_ids: list[str] | None = None, limit: int = 3
) -> dict[str, list[str]]:
"""Return bounded LLM payloads for only the sessions needing fallback."""
if session_ids is not None and not session_ids:
return {}
session_filter = ""
params: list[Any] = []
if session_ids is not None:
placeholders = ",".join("?" for _ in session_ids)
session_filter = f" AND session_id IN ({placeholders})"
params.extend(session_ids)
params.append(limit)
with db_session(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT session_id, payload FROM ("
" SELECT session_id, payload, ROW_NUMBER() OVER ("
" PARTITION BY session_id ORDER BY timestamp ASC"
" ) AS row_number FROM traces WHERE type = 'llm_call'"
f"{session_filter}"
") WHERE row_number <= ?",
params,
)
result: dict[str, list[str]] = {}
for row in cursor.fetchall():
session_id = str(row[0] or "")
payload = row[1]
if session_id and payload:
result.setdefault(session_id, []).append(payload)
return result
def get_agent_trace_names(self, session_id: str) -> list[str]:
try:
with db_session(self.db_path) as conn:
@@ -127,6 +198,23 @@ class SessionRepository:
except Exception:
return []
def get_agent_trace_names_map(self) -> dict[str, list[str]]:
"""Return distinct agent/LLM trace names grouped by session."""
with db_session(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT session_id, name FROM traces "
"WHERE type IN ('agent', 'llm_call') AND session_id IS NOT NULL "
"GROUP BY session_id, name"
)
result: dict[str, list[str]] = {}
for row in cursor.fetchall():
session_id = str(row[0] or "")
name = row[1]
if session_id and name:
result.setdefault(session_id, []).append(name)
return result
def get_session_by_id(self, session_id: str) -> dict[str, Any] | None:
try:
with db_session(self.db_path) as conn:
@@ -163,7 +163,12 @@ class StepRepository:
co = (len(resp_text) // 4) + (resp_images * 258)
return int(pr), int(co), int(pr + co)
def _clean_tool_payload(self, payload_raw: Any, trace_type: str | None = None) -> Any:
def _clean_tool_payload(
self,
payload_raw: Any,
trace_type: str | None = None,
trace_name: str | None = None,
) -> Any:
if not payload_raw:
return None
payload_obj = payload_raw
@@ -204,8 +209,18 @@ class StepRepository:
)
result_payload: dict[str, Any] = {"args": cleaned_args}
# Extract structured action execution results
# Extract structured action execution results. Video analysis keeps its
# result because the task stream needs to distinguish cached, partial,
# recovering, waiting, completed, and terminal outcomes. Other tools
# retain the historical compact payload to avoid sending large blobs.
res = payload_obj.get("result")
is_video_analysis = str(trace_name or "").lower() in {
"video_analysis",
"video_analyzer",
"video_analyzer_pure",
"spawn_sub_agent",
"analyze_audio_only",
}
if isinstance(res, dict):
if res.get("post_image_name"):
result_payload["post_image_name"] = res["post_image_name"]
@@ -213,8 +228,12 @@ class StepRepository:
result_payload["pre_image_name"] = res["pre_image_name"]
if res.get("outcome"):
result_payload["outcome"] = res["outcome"]
if is_video_analysis:
result_payload["result"] = self._clean_value(res)
elif res:
res_str = str(res)
if is_video_analysis:
result_payload["result"] = res_str
match = re.search(r"([a-f0-9]{64})", res_str)
if match:
result_payload["post_image_name"] = match.group(1)
@@ -259,7 +278,7 @@ class StepRepository:
return trace_dict
trace_dict["payload"] = self._clean_tool_payload(
trace_dict.get("payload"), trace_dict.get("type")
trace_dict.get("payload"), trace_dict.get("type"), trace_dict.get("name")
)
return trace_dict
+31 -6
View File
@@ -40,14 +40,18 @@ async def list_sessions():
try:
rows = session_repo.get_all_sessions()
video_rec_map = session_repo.get_video_recordings_map()
latest_recordings = session_repo.get_latest_video_recordings_map()
agent_names_by_session = session_repo.get_agent_trace_names_map()
video_idx = media_service.build_video_index()
default_model_info = model_service.get_active_model_info()
orphaned_ids = []
unresolved_profiles = []
result = []
for row_dict in rows:
s_id = str(row_dict.get("session_id"))
recording = session_repo.get_video_recording_for_session(s_id)
recording = latest_recordings.get(s_id)
if row_dict.get("status") == "running":
is_active = s_id in state.active_connections or (
state.is_running and s_id == str(state.active_session_id)
@@ -66,15 +70,36 @@ async def list_sessions():
else None
)
llm_traces = session_repo.get_llm_traces_for_profile(s_id)
agent_names = session_repo.get_agent_trace_names(s_id)
agent_names = agent_names_by_session.get(s_id, [])
sess_profile = model_service.resolve_session_profile(
row_dict, llm_traces, state.current_profile, agent_names=agent_names
row_dict, None, state.current_profile, agent_names=agent_names
)
row_dict["model_info"] = model_service.get_active_model_info(sess_profile)
row_dict["model_info"] = (
model_service.get_active_model_info(sess_profile)
if sess_profile
else default_model_info
)
if not sess_profile:
unresolved_profiles.append(row_dict)
result.append(row_dict)
# Most sessions carry a profile in device_info or an agent trace name.
# Only inspect the much larger LLM payload for legacy rows that remain
# ambiguous after those cheap checks.
if unresolved_profiles:
unresolved_ids = [str(row.get("session_id")) for row in unresolved_profiles]
llm_traces_by_session = session_repo.get_llm_traces_for_profiles_map(unresolved_ids)
for row_dict in unresolved_profiles:
s_id = str(row_dict.get("session_id"))
llm_traces = llm_traces_by_session.get(s_id)
if not llm_traces:
continue
sess_profile = model_service.resolve_session_profile(
row_dict, llm_traces, state.current_profile
)
if sess_profile:
row_dict["model_info"] = model_service.get_active_model_info(sess_profile)
if orphaned_ids:
try:
harvested = session_repo.harvest_orphaned_sessions(orphaned_ids)
+9 -6
View File
@@ -30,9 +30,9 @@ class SelectDeviceRequest(BaseModel):
@router.get("/readiness", response_model=SystemReadinessReport)
async def get_system_readiness() -> SystemReadinessReport:
async def get_system_readiness(force: bool = False) -> SystemReadinessReport:
"""Execute all diagnostic probes and return a comprehensive system readiness report."""
return await readiness_engine.run_all()
return await readiness_engine.run_all(force_refresh=force)
@router.post("/devices/select")
@@ -44,7 +44,7 @@ async def select_active_device(request: SelectDeviceRequest):
readiness_engine.set_active_device_serial(serial)
# Return updated readiness
report = await readiness_engine.run_all()
report = await readiness_engine.run_all(force_refresh=True)
return {
"status": "success",
"selected_serial": serial,
@@ -56,7 +56,8 @@ async def select_active_device(request: SelectDeviceRequest):
async def restart_adb_server():
"""Restart local ADB server and return an updated readiness check."""
restart_result = await readiness_engine.restart_adb_server()
updated_report = await readiness_engine.run_all()
readiness_engine.invalidate_cache()
updated_report = await readiness_engine.run_all(force_refresh=True)
return {
"restart_result": restart_result,
"report": updated_report,
@@ -85,7 +86,8 @@ class ConnectAdbRequest(BaseModel):
async def connect_wireless_adb(request: ConnectAdbRequest):
"""Connect to a device over Wi-Fi and return updated readiness."""
connect_result = await readiness_engine.connect_wireless_adb(request.host, request.port)
updated_report = await readiness_engine.run_all()
readiness_engine.invalidate_cache()
updated_report = await readiness_engine.run_all(force_refresh=True)
return {
"connect_result": connect_result,
"report": updated_report,
@@ -222,7 +224,8 @@ async def update_credentials(request: UpdateCredentialsRequest):
settings.set_api_key(provider, key, persist_to_env=request.persist_to_env)
# Re-run all diagnostic probes to build updated report
updated_report = await readiness_engine.run_all()
readiness_engine.invalidate_cache()
updated_report = await readiness_engine.run_all(force_refresh=True)
action_desc = (
"successfully verified, updated, and applied" if key else "successfully cleared"
)