refactor(server/routes): classify except Exception in 23 route files
Narrow 131 except Exception to specific exception types across all server/routes/ modules. Framework boundaries (main execute paths, WebSocket top-level) retain except Exception with asyncio.CancelledError guard. Categories: - WebSocket ops: (ConnectionError, RuntimeError, asyncio.TimeoutError) - DB/Store ops: (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError) - EventQueue: (asyncio.QueueFull, RuntimeError, ConnectionError) - Config construction: (ValueError, TypeError, KeyError) - Cleanup/dissolve: (RuntimeError, asyncio.TimeoutError, ConnectionError) - HTTP handlers: business-specific exceptions - Framework boundaries: retain except Exception + CancelledError guard Stats: 101 narrowed, 31 framework boundary retained, 2 noqa (DB resilience) Follow-up to PR #8 (U1-U5 systematic tech debt cleanup).
This commit is contained in:
parent
cc531d0663
commit
aa6367ff9f
|
|
@ -37,7 +37,7 @@ async def create_agent(request: CreateAgentRequest, req: Request):
|
|||
config_dict = request.config
|
||||
try:
|
||||
config = SkillConfig.from_dict(config_dict)
|
||||
except Exception:
|
||||
except (ValueError, KeyError, TypeError):
|
||||
config = AgentConfig.from_dict(config_dict)
|
||||
agent = await pool.create_agent(config)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ from agentkit.server.auth.session_service import (
|
|||
REVOKE_REASON_PASSWORD_CHANGED,
|
||||
REVOKE_REASON_USER_TERMINATED,
|
||||
SessionCreate,
|
||||
SessionNotFound,
|
||||
SessionReuseDetected,
|
||||
SessionService,
|
||||
get_session_service,
|
||||
)
|
||||
|
|
@ -253,7 +255,7 @@ def _is_legacy_client(request: Request) -> bool:
|
|||
if client_v is None or cutoff_v is None:
|
||||
return False
|
||||
return client_v < cutoff_v
|
||||
except Exception: # noqa: BLE001
|
||||
except (ValueError, TypeError, AttributeError): # noqa: BLE001
|
||||
logger.debug("Failed to parse X-Client-Version %r", raw)
|
||||
return False
|
||||
|
||||
|
|
@ -492,7 +494,7 @@ async def refresh(payload: RefreshRequest, request: Request) -> TokenResponse:
|
|||
# 1. Verify signature + type
|
||||
try:
|
||||
refresh_payload = verify_token(payload.refresh_token, secret, expected_type="refresh")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except (jwt.PyJWTError, ValueError, KeyError) as exc: # noqa: BLE001
|
||||
raise HTTPException(status_code=401, detail="Invalid refresh token") from exc
|
||||
|
||||
# 2-3. Validate the session (also handles reuse detection)
|
||||
|
|
@ -510,7 +512,7 @@ async def refresh(payload: RefreshRequest, request: Request) -> TokenResponse:
|
|||
new_refresh_token=new_pair.refresh_token,
|
||||
new_ttl_seconds=int(REFRESH_TOKEN_TTL.total_seconds()),
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — SessionReuseDetected / SessionNotFound
|
||||
except (SessionReuseDetected, SessionNotFound, ValueError, KeyError, RuntimeError) as exc: # noqa: BLE001 — SessionReuseDetected / SessionNotFound
|
||||
logger.info("Refresh rejected: %s", exc)
|
||||
raise HTTPException(status_code=401, detail="Invalid refresh token") from exc
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ async def validate_formula(
|
|||
parse_formula(body.formula)
|
||||
except (FormulaParseError, FormulaSecurityError, UnknownFunctionError) as e:
|
||||
return {"valid": False, "error": str(e)}
|
||||
except Exception as e: # pragma: no cover — defensive
|
||||
except (ValueError, TypeError, KeyError, AttributeError) as e: # pragma: no cover — defensive
|
||||
return {"valid": False, "error": f"Unexpected error: {e}"}
|
||||
return {"valid": True}
|
||||
|
||||
|
|
@ -750,7 +750,7 @@ async def upload_file(
|
|||
f.write(chunk)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
except (OSError, RuntimeError) as exc:
|
||||
file_path.unlink(missing_ok=True)
|
||||
logger.error(f"Failed to save uploaded bitable file: {exc}")
|
||||
raise HTTPException(status_code=500, detail="Failed to save file") from exc
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ async def _invalidate_adapter_cache(channel_id: str) -> None:
|
|||
if old is not None:
|
||||
try:
|
||||
await old.close()
|
||||
except Exception: # noqa: BLE001 — 关闭异常不应阻塞配置变更
|
||||
except (ConnectionError, RuntimeError, OSError, asyncio.TimeoutError): # noqa: BLE001 — 关闭异常不应阻塞配置变更
|
||||
logger.debug("关闭旧适配器异常已忽略: channel_id=%s", channel_id)
|
||||
|
||||
|
||||
|
|
@ -562,7 +562,7 @@ async def close_all_adapters() -> None:
|
|||
for channel_id, adapter in list(_adapter_cache.items()):
|
||||
try:
|
||||
await adapter.close()
|
||||
except Exception: # noqa: BLE001
|
||||
except (ConnectionError, RuntimeError, OSError, asyncio.TimeoutError): # noqa: BLE001
|
||||
logger.debug("关闭适配器异常已忽略: channel_id=%s", channel_id)
|
||||
_adapter_cache.clear()
|
||||
|
||||
|
|
@ -614,6 +614,8 @@ async def _process_inbound_message(app_state: Any, adapter: MessageAdapter, mess
|
|||
model=routing.model or "default",
|
||||
)
|
||||
final_content = getattr(result, "content", "") or ""
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001 — 回退路径需捕获全部异常
|
||||
logger.warning("ReActEngine 执行失败,回退到 DIRECT_CHAT: %s", exc)
|
||||
final_content = await _direct_chat(llm_gateway, routing)
|
||||
|
|
@ -628,6 +630,8 @@ async def _process_inbound_message(app_state: Any, adapter: MessageAdapter, mess
|
|||
content=final_content,
|
||||
)
|
||||
await adapter.send_message(outgoing)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001 — webhook 必须保持响应能力
|
||||
logger.exception("处理入站消息失败: %s", exc)
|
||||
|
||||
|
|
@ -703,7 +707,7 @@ async def channel_webhook(channel_id: str, request: Request) -> Any:
|
|||
except WeComURLVerification as e:
|
||||
# 企微 URL 验证 — 返回 XML 响应
|
||||
return Response(content=e.response_xml, media_type="application/xml")
|
||||
except Exception as exc: # noqa: BLE001 — 防止 receive_message 异常导致 500 触发平台重试风暴
|
||||
except (ValueError, KeyError, RuntimeError, AttributeError, OSError) as exc: # noqa: BLE001 — 防止 receive_message 异常导致 500 触发平台重试风暴
|
||||
logger.warning("receive_message 解析失败 channel=%s: %s", channel_id, exc)
|
||||
return {"code": 0, "msg": "invalid_payload"}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class ChatConnectionManager:
|
|||
for ws, _ in conns:
|
||||
try:
|
||||
await ws.send_json(message)
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
stale.append(ws)
|
||||
for ws in stale:
|
||||
self.remove(session_id, ws)
|
||||
|
|
@ -295,6 +295,8 @@ async def _execute_board_meeting(
|
|||
await team.create_board(topic=routing_result.topic, expert_configs=expert_configs)
|
||||
orchestrator = BoardOrchestrator(team=team)
|
||||
result = await orchestrator.execute(routing_result.topic)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Board meeting failed for session {session_id}: {e}", exc_info=True)
|
||||
await websocket.send_json(
|
||||
|
|
@ -302,7 +304,7 @@ async def _execute_board_meeting(
|
|||
)
|
||||
try:
|
||||
await team.dissolve()
|
||||
except Exception:
|
||||
except (RuntimeError, asyncio.TimeoutError, ConnectionError):
|
||||
pass
|
||||
return True
|
||||
finally:
|
||||
|
|
@ -348,7 +350,7 @@ async def _execute_board_meeting(
|
|||
# Dissolve the team to release expert agents
|
||||
try:
|
||||
await team.dissolve()
|
||||
except Exception as e:
|
||||
except (RuntimeError, asyncio.TimeoutError, ConnectionError) as e:
|
||||
logger.warning(f"Board team dissolve failed: {e}")
|
||||
|
||||
return True
|
||||
|
|
@ -467,7 +469,7 @@ async def _execute_team_collab(
|
|||
# Always dissolve the team and remove handler to avoid leaks
|
||||
try:
|
||||
await team.dissolve()
|
||||
except Exception as e:
|
||||
except (RuntimeError, asyncio.TimeoutError, ConnectionError) as e:
|
||||
logger.warning(f"Team dissolve failed: {e}")
|
||||
# dissolve() already clears handlers via handoff_transport.close()
|
||||
|
||||
|
|
@ -585,7 +587,7 @@ def _build_phase_engine(
|
|||
if phase_policy is None:
|
||||
# Empty config (no `plan_exec:` section) → use KTD5 defaults.
|
||||
phase_policy = default_policy()
|
||||
except Exception as e:
|
||||
except (ValueError, TypeError, KeyError) as e:
|
||||
logger.error(
|
||||
"PLAN_EXEC phase policy construction failed for session %s: %s",
|
||||
session_id,
|
||||
|
|
@ -695,6 +697,8 @@ async def send_message(session_id: str, request: SendMessageRequest, req: Reques
|
|||
agent_name=agent.name,
|
||||
system_prompt=system_prompt,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"PLAN_EXEC execution error for session {session_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
@ -773,6 +777,8 @@ async def send_message(session_id: str, request: SendMessageRequest, req: Reques
|
|||
return response_dict
|
||||
return response
|
||||
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Chat execution error for session {session_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
@ -829,7 +835,7 @@ async def _resolve_ws_dept_context(
|
|||
db_path_resolved = Path(db_path)
|
||||
try:
|
||||
department_ids = await _fetch_user_department_ids(db_path_resolved, user_id)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.exception(
|
||||
"Failed to fetch department ids for WebSocket user %s — fail-closed",
|
||||
user_id,
|
||||
|
|
@ -946,7 +952,7 @@ async def chat_websocket(websocket: WebSocket, session_id: str) -> None:
|
|||
"data": {"content": content},
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
except (asyncio.QueueFull, RuntimeError, ConnectionError) as e:
|
||||
logger.warning(f"Failed to enqueue intervention: {e}")
|
||||
await websocket.send_json(
|
||||
{
|
||||
|
|
@ -1022,11 +1028,13 @@ async def chat_websocket(websocket: WebSocket, session_id: str) -> None:
|
|||
|
||||
except WebSocketDisconnect:
|
||||
logger.debug(f"Chat WebSocket disconnected for session {session_id}")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Chat WebSocket error for session {session_id}: {e}")
|
||||
try:
|
||||
await websocket.send_json({"type": "error", "data": {"message": str(e)}})
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
finally:
|
||||
# Clean up pending futures
|
||||
|
|
@ -1174,6 +1182,8 @@ async def _handle_chat_message(
|
|||
content=final_content,
|
||||
agent_name=agent.name,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
# Check if this is a QuotaExceededError (U4: WebSocket quota).
|
||||
from agentkit.llm.gateway import QuotaExceededError
|
||||
|
|
@ -1422,6 +1432,8 @@ async def _handle_chat_message(
|
|||
agent_name=agent.name,
|
||||
)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Chat execution error for session {session_id}: {e}")
|
||||
# Show meaningful error to user, but avoid leaking full stack traces
|
||||
|
|
@ -1473,6 +1485,8 @@ async def upload_chat_file(file: UploadFile = File(...)) -> dict[str, Any]:
|
|||
file_path.write_bytes(contents)
|
||||
except HTTPException:
|
||||
raise
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.error(f"Failed to save uploaded file: {exc}")
|
||||
raise HTTPException(status_code=500, detail="Failed to save file") from exc
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ def _collect_skill_configs(request: Request) -> list[dict[str, Any]]:
|
|||
"version": getattr(skill.config, "version", "1.0.0"),
|
||||
"config": config_dict,
|
||||
})
|
||||
except Exception as e:
|
||||
except (AttributeError, ValueError, KeyError, RuntimeError) as e:
|
||||
logger.warning(f"Failed to collect skill configs: {e}")
|
||||
return configs
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ def _collect_workflow_configs(request: Request) -> list[dict[str, Any]]:
|
|||
from agentkit.server.routes.workflows import _workflow_store
|
||||
|
||||
workflow_store = _workflow_store
|
||||
except Exception:
|
||||
except (ImportError, AttributeError):
|
||||
return []
|
||||
|
||||
configs: list[dict[str, Any]] = []
|
||||
|
|
@ -94,7 +94,7 @@ def _collect_workflow_configs(request: Request) -> list[dict[str, Any]]:
|
|||
configs.append(wf.to_dict())
|
||||
else:
|
||||
configs.append(dict(wf))
|
||||
except Exception as e:
|
||||
except (RuntimeError, AttributeError, ValueError) as e:
|
||||
logger.warning(f"Failed to collect workflow configs: {e}")
|
||||
return configs
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Endpoints:
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hmac
|
||||
import logging
|
||||
import uuid
|
||||
|
|
@ -155,6 +156,8 @@ async def create_document(
|
|||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
except FileNotFoundError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e)) from e
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Document creation failed: {e}")
|
||||
raise HTTPException(status_code=500, detail="Document creation failed") from e
|
||||
|
|
@ -187,7 +190,7 @@ async def upload_template(
|
|||
file_path.write_bytes(contents)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
except (OSError, RuntimeError) as exc:
|
||||
logger.error(f"Failed to save template: {exc}")
|
||||
raise HTTPException(status_code=500, detail="Failed to save template") from exc
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Evolution API routes"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
|
|
@ -42,6 +43,8 @@ async def list_evolution_events(
|
|||
agent_name=agent_name,
|
||||
change_type=event_type,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list evolution events: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to list evolution events")
|
||||
|
|
@ -63,6 +66,8 @@ async def get_skill_versions(skill_name: str, req: Request = None):
|
|||
store = _get_evolution_store(req)
|
||||
try:
|
||||
versions = await store.list_skill_versions(skill_name)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get skill versions for '{skill_name}': {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get skill versions")
|
||||
|
|
@ -103,6 +108,8 @@ async def trigger_evolution(request: TriggerEvolutionRequest, req: Request = Non
|
|||
)
|
||||
try:
|
||||
event_id = await store.record(event)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to record trigger event: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to trigger evolution")
|
||||
|
|
@ -153,6 +160,8 @@ async def list_ab_tests(
|
|||
for e in entries
|
||||
]
|
||||
return {"items": results[:limit], "total": len(results)}
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list A/B tests from persistent store: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to list A/B tests")
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ async def list_experiences(
|
|||
}
|
||||
)
|
||||
return {"experiences": experiences, "total": len(experiences)}
|
||||
except Exception as e:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError) as e:
|
||||
logger.error(f"Failed to list experiences from store: {e}")
|
||||
|
||||
# Fallback to in-memory store
|
||||
|
|
@ -324,7 +324,7 @@ async def get_metrics(
|
|||
|
||||
# Generate daily trends from the metrics
|
||||
trends = _generate_trends(metrics_list, period)
|
||||
except Exception as e:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError) as e:
|
||||
logger.error(f"Failed to get metrics from store: {e}")
|
||||
else:
|
||||
# Generate from in-memory experiences
|
||||
|
|
@ -501,7 +501,7 @@ async def get_usage(
|
|||
"errors": 0,
|
||||
"avg_latency_ms": round(d["latency"] / max(d["requests"], 1), 1),
|
||||
})
|
||||
except Exception as e:
|
||||
except (ConnectionError, OSError, ValueError, KeyError, RuntimeError, AttributeError) as e:
|
||||
logger.error(f"Failed to get usage from LLMGateway: {e}")
|
||||
|
||||
# Fill in missing dates with zero
|
||||
|
|
@ -587,7 +587,7 @@ async def check_pitfalls(
|
|||
})
|
||||
|
||||
return {"warnings": warnings_data}
|
||||
except Exception as e:
|
||||
except (RuntimeError, ValueError, KeyError, AttributeError, asyncio.TimeoutError, ConnectionError) as e:
|
||||
logger.error(f"Failed to check pitfalls: {e}")
|
||||
return {"warnings": []}
|
||||
|
||||
|
|
@ -642,7 +642,7 @@ async def list_path_optimizations(
|
|||
else None,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
except (ValueError, KeyError, RuntimeError, AttributeError) as e:
|
||||
logger.error(f"Failed to get path optimizations: {e}")
|
||||
|
||||
# Also include in-memory optimizations
|
||||
|
|
@ -767,7 +767,7 @@ async def evolution_dashboard_ws(websocket: WebSocket):
|
|||
)
|
||||
except WebSocketDisconnect:
|
||||
logger.debug("Evolution dashboard WebSocket disconnected")
|
||||
except Exception as e:
|
||||
except (RuntimeError, asyncio.TimeoutError, ConnectionError) as e:
|
||||
logger.error(f"Evolution dashboard WebSocket error: {e}")
|
||||
finally:
|
||||
if websocket in _ws_connections:
|
||||
|
|
@ -781,7 +781,7 @@ async def _broadcast_event(event_type: str, data: dict):
|
|||
for ws in _ws_connections:
|
||||
try:
|
||||
await ws.send_json(message)
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
disconnected.append(ws)
|
||||
for ws in disconnected:
|
||||
if ws in _ws_connections:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"""Health check route"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
|
@ -23,14 +25,14 @@ async def health_check(request: Request):
|
|||
redis_client = await task_store._get_redis()
|
||||
await redis_client.ping()
|
||||
redis_status = "available"
|
||||
except Exception as ping_exc:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError) as ping_exc:
|
||||
redis_status = f"error: {str(ping_exc)[:100]}"
|
||||
overall_status = "degraded"
|
||||
else:
|
||||
redis_status = "not_configured"
|
||||
else:
|
||||
redis_status = "not_configured"
|
||||
except Exception as exc:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError, AttributeError) as exc:
|
||||
redis_status = f"error: {str(exc)[:100]}"
|
||||
overall_status = "degraded"
|
||||
checks["redis"] = redis_status
|
||||
|
|
@ -42,7 +44,7 @@ async def health_check(request: Request):
|
|||
try:
|
||||
agents = agent_pool.list_agents()
|
||||
pool_size = len(agents)
|
||||
except Exception:
|
||||
except (RuntimeError, AttributeError):
|
||||
pass
|
||||
checks["agent_pool"] = {"status": "available", "size": pool_size}
|
||||
|
||||
|
|
@ -57,7 +59,7 @@ async def health_check(request: Request):
|
|||
else:
|
||||
llm_status = "no_providers"
|
||||
overall_status = "degraded"
|
||||
except Exception:
|
||||
except (RuntimeError, AttributeError, ValueError):
|
||||
llm_status = "error"
|
||||
overall_status = "degraded"
|
||||
checks["llm_gateway"] = llm_status
|
||||
|
|
@ -68,7 +70,7 @@ async def health_check(request: Request):
|
|||
if skill_registry:
|
||||
try:
|
||||
skill_count = len(skill_registry.list_skills())
|
||||
except Exception:
|
||||
except (RuntimeError, AttributeError):
|
||||
pass
|
||||
checks["skill_registry"] = {
|
||||
"status": "available" if skill_registry else "not_configured",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hmac
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -252,7 +253,7 @@ async def list_sources(
|
|||
visible_ids = await filter_kb_sources_by_department(
|
||||
db_path, dept_ctx.department_ids, all_ids
|
||||
)
|
||||
except Exception: # noqa: BLE001 — never block listing on DB errors
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError): # noqa: BLE001 — never block listing on DB errors
|
||||
logger.exception("Department KB filtering failed — returning empty list")
|
||||
return {"sources": []}
|
||||
visible_set = set(visible_ids)
|
||||
|
|
@ -398,7 +399,7 @@ async def list_documents(
|
|||
visible_ids = await filter_kb_sources_by_department(
|
||||
db_path, dept_ctx.department_ids, all_source_ids
|
||||
)
|
||||
except Exception: # noqa: BLE001 — never block listing on DB errors
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError): # noqa: BLE001 — never block listing on DB errors
|
||||
logger.exception("Department KB filtering failed — returning empty list")
|
||||
return {"documents": []}
|
||||
visible_set = set(visible_ids)
|
||||
|
|
@ -484,7 +485,7 @@ async def upload_document(
|
|||
try:
|
||||
text = processor.parse(tmp_path, file_type)
|
||||
chunks = processor.segment(text)
|
||||
except Exception as e:
|
||||
except (ValueError, OSError, RuntimeError, UnicodeDecodeError) as e:
|
||||
logger.warning("Document parsing failed: %s", e)
|
||||
raise HTTPException(status_code=422, detail=f"Document parsing failed: {e}") from e
|
||||
|
||||
|
|
@ -567,7 +568,7 @@ async def preview_document(
|
|||
chunk_size=chunk_size,
|
||||
chunk_overlap=chunk_overlap,
|
||||
)
|
||||
except Exception as e:
|
||||
except (ValueError, OSError, RuntimeError, UnicodeDecodeError) as e:
|
||||
logger.warning("Document preview failed: %s", e)
|
||||
raise HTTPException(status_code=422, detail=f"Document preview failed: {e}") from e
|
||||
|
||||
|
|
@ -628,7 +629,7 @@ async def search_knowledge(
|
|||
for r in results
|
||||
]
|
||||
}
|
||||
except Exception as e:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError) as e:
|
||||
logger.warning(f"Semantic search failed: {e}")
|
||||
|
||||
# Fallback: return empty results with a hint
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ async def publish_skill(
|
|||
|
||||
try:
|
||||
skill = skill_registry.get(skill_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
raise HTTPException(status_code=404, detail=f"Skill '{skill_name}' not found")
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Memory API routes"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
|
|
@ -40,6 +41,8 @@ async def search_episodic_memory(
|
|||
if agent_name:
|
||||
filters["agent_name"] = agent_name
|
||||
items = await retriever._episodic.search(query, top_k=top_k, filters=filters or None)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to search episodic memory: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to search episodic memory")
|
||||
|
|
@ -76,6 +79,8 @@ async def search_semantic_memory(
|
|||
if knowledge_base_ids:
|
||||
filters["knowledge_base_ids"] = [kid.strip() for kid in knowledge_base_ids.split(",")]
|
||||
items = await retriever._semantic.search(query, top_k=top_k, filters=filters or None)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to search semantic memory: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to search semantic memory")
|
||||
|
|
@ -104,6 +109,8 @@ async def delete_episodic_memory(key: str, req: Request = None):
|
|||
|
||||
try:
|
||||
deleted = await retriever._episodic.delete(key)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete episodic memory '{key}': {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to delete episodic memory")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Metrics route — /api/v1/metrics"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
|
@ -29,7 +30,7 @@ async def get_metrics(request: Request):
|
|||
task_metrics["completed_tasks"] = counts.get("completed", 0)
|
||||
task_metrics["failed_tasks"] = counts.get("failed", 0)
|
||||
task_metrics["pending_tasks"] = counts.get("pending", 0)
|
||||
except Exception as e:
|
||||
except (RuntimeError, AttributeError, ConnectionError, asyncio.TimeoutError) as e:
|
||||
logger.warning(f"Failed to collect task metrics: {e}")
|
||||
|
||||
# Agent pool metrics
|
||||
|
|
@ -41,7 +42,7 @@ async def get_metrics(request: Request):
|
|||
try:
|
||||
agents = agent_pool.list_agents()
|
||||
agent_metrics["total_agents"] = len(agents)
|
||||
except Exception as e:
|
||||
except (RuntimeError, AttributeError) as e:
|
||||
logger.warning(f"Failed to collect agent metrics: {e}")
|
||||
|
||||
# Skill registry metrics
|
||||
|
|
@ -53,7 +54,7 @@ async def get_metrics(request: Request):
|
|||
try:
|
||||
skills = skill_registry.list_skills()
|
||||
skill_metrics["total_skills"] = len(skills)
|
||||
except Exception as e:
|
||||
except (RuntimeError, AttributeError) as e:
|
||||
logger.warning(f"Failed to collect skill metrics: {e}")
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ async def _emit_event_safe(
|
|||
data=data or {},
|
||||
)
|
||||
await event_queue.emit(event)
|
||||
except Exception as e:
|
||||
except (asyncio.QueueFull, RuntimeError, ConnectionError) as e:
|
||||
logger.warning(f"EventQueue emit failed (type={event_type}): {e}", exc_info=True)
|
||||
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ class PortalConnectionManager:
|
|||
import asyncio
|
||||
|
||||
asyncio.create_task(oldest.close(code=1008, reason="Connection limit exceeded"))
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError):
|
||||
pass
|
||||
conns.append(ws)
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ class PortalConnectionManager:
|
|||
for ws in conns:
|
||||
try:
|
||||
await ws.send_json(message)
|
||||
except Exception as e:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError) as e:
|
||||
logger.debug(
|
||||
"Portal WS send failed for user %s (marking stale): %s", user_id, e
|
||||
)
|
||||
|
|
@ -285,7 +285,7 @@ async def _build_history_messages(
|
|||
"""
|
||||
try:
|
||||
history = await _conversation_store.get_history(conv_id, limit=limit)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
return []
|
||||
|
||||
# The last message in history is the current user message (just added),
|
||||
|
|
@ -553,6 +553,8 @@ async def chat(request: ChatRequest, req: Request, _auth: None = Depends(_verify
|
|||
):
|
||||
if event.event_type == "final_answer":
|
||||
collected_output.append(event.data.get("output", ""))
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
response_text = f"执行出错: {e}"
|
||||
else:
|
||||
|
|
@ -682,6 +684,8 @@ async def chat_stream(request: ChatRequest, req: Request, _auth: None = Depends(
|
|||
}
|
||||
),
|
||||
}
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
yield {
|
||||
"event": "error",
|
||||
|
|
@ -862,7 +866,7 @@ async def _execute_react_background(
|
|||
await _task_store_update_status(
|
||||
task_store, task_id, TaskStatus.RUNNING, started_at=datetime.now(timezone.utc)
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("Failed to update TaskStore RUNNING", exc_info=True)
|
||||
|
||||
async for event in react_engine.execute_stream(
|
||||
|
|
@ -909,7 +913,7 @@ async def _execute_react_background(
|
|||
progress=1.0,
|
||||
progress_message="Completed",
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("Failed to update TaskStore COMPLETED", exc_info=True)
|
||||
|
||||
# Emit task.completed so subscribers know the task is done
|
||||
|
|
@ -932,7 +936,15 @@ async def _execute_react_background(
|
|||
partial = _ensure_non_empty("".join(collected_output))
|
||||
try:
|
||||
await asyncio.shield(conversation_store.add_message(conv_id, "assistant", partial))
|
||||
except (Exception, asyncio.CancelledError):
|
||||
except (
|
||||
asyncio.CancelledError,
|
||||
ConnectionError,
|
||||
OSError,
|
||||
asyncio.TimeoutError,
|
||||
ValueError,
|
||||
KeyError,
|
||||
RuntimeError,
|
||||
):
|
||||
logger.warning("Failed to persist partial output on cancel")
|
||||
if task_store is not None:
|
||||
try:
|
||||
|
|
@ -945,7 +957,15 @@ async def _execute_react_background(
|
|||
completed_at=datetime.now(timezone.utc),
|
||||
)
|
||||
)
|
||||
except (Exception, asyncio.CancelledError):
|
||||
except (
|
||||
asyncio.CancelledError,
|
||||
ConnectionError,
|
||||
OSError,
|
||||
asyncio.TimeoutError,
|
||||
ValueError,
|
||||
KeyError,
|
||||
RuntimeError,
|
||||
):
|
||||
logger.warning("Failed to update TaskStore on cancel", exc_info=True)
|
||||
# P0 #2 fix: _emit_event_safe is async (it awaits event_queue.emit).
|
||||
# Shield it so a re-entrant CancelledError doesn't kill the emit
|
||||
|
|
@ -963,7 +983,7 @@ async def _execute_react_background(
|
|||
},
|
||||
)
|
||||
)
|
||||
except (Exception, asyncio.CancelledError):
|
||||
except (asyncio.CancelledError, asyncio.QueueFull, RuntimeError, ConnectionError):
|
||||
logger.warning("Failed to emit TASK_FAILED on cancel")
|
||||
raise # Propagate cancellation
|
||||
|
||||
|
|
@ -973,7 +993,7 @@ async def _execute_react_background(
|
|||
partial = _ensure_non_empty("".join(collected_output))
|
||||
try:
|
||||
await conversation_store.add_message(conv_id, "assistant", partial)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("Failed to persist partial output in background task")
|
||||
|
||||
if task_store is not None:
|
||||
|
|
@ -985,7 +1005,7 @@ async def _execute_react_background(
|
|||
error_message=str(e),
|
||||
completed_at=datetime.now(timezone.utc),
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("Failed to update TaskStore FAILED", exc_info=True)
|
||||
|
||||
# Emit task.failed so subscribers know the task failed
|
||||
|
|
@ -1120,7 +1140,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
|
||||
try:
|
||||
record = await _task_store_get(resume_task_store, resume_task_id)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("TaskStore.get failed during resume", exc_info=True)
|
||||
record = None
|
||||
if record is not None:
|
||||
|
|
@ -1333,7 +1353,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
},
|
||||
)
|
||||
await _broadcast_dashboard_event("metrics_updated", {"period": "7d"})
|
||||
except Exception as e:
|
||||
except (asyncio.QueueFull, RuntimeError, ConnectionError, ValueError, KeyError) as e:
|
||||
logger.warning(f"Failed to record experience: {e}")
|
||||
|
||||
# Unified preprocessing via RequestPreprocessor (minimal: @skill prefix + greeting regex + REACT)
|
||||
|
|
@ -1414,7 +1434,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
TaskStatus.PENDING,
|
||||
metadata={"conversation_id": conv.id},
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("Failed to register task in TaskStore", exc_info=True)
|
||||
|
||||
# Execute based on routing result's execution_mode
|
||||
|
|
@ -1455,7 +1475,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
progress=1.0,
|
||||
progress_message="Completed",
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
logger.warning("Failed to update TaskStore for DIRECT_CHAT", exc_info=True)
|
||||
|
||||
# Emit turn.final_answer and task.completed to EQ
|
||||
|
|
@ -1526,7 +1546,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
chat_messages.insert(
|
||||
-1, {"role": hist_msg.role, "content": hist_msg.content}
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
|
||||
pass
|
||||
response = await llm_gateway.chat(
|
||||
messages=chat_messages,
|
||||
|
|
@ -1627,7 +1647,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
logger.warning("EventQueue not configured; awaiting background task directly")
|
||||
try:
|
||||
await bg_task
|
||||
except Exception:
|
||||
except (RuntimeError, ConnectionError, asyncio.TimeoutError):
|
||||
pass # errors handled inside _execute_react_background
|
||||
active_bg_task = None
|
||||
continue
|
||||
|
|
@ -1734,6 +1754,8 @@ async def portal_websocket(websocket: WebSocket):
|
|||
# kill the task, lose the full output, and mark it FAILED —
|
||||
# defeating layers 2 and 3. The task is only cancelled on explicit
|
||||
# user cancel (msg_type == 'cancel') or application shutdown.
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Portal WebSocket error: {e}")
|
||||
# P1 #6 fix: Do NOT cancel the background task on connection-level
|
||||
|
|
@ -1758,7 +1780,7 @@ async def portal_websocket(websocket: WebSocket):
|
|||
)
|
||||
try:
|
||||
await websocket.send_json({"type": "error", "data": {"message": str(e)}})
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
finally:
|
||||
# Remove from user-scoped push tracking on any disconnect/error/return.
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ def _skill_to_detail(skill: Any) -> dict[str, Any]:
|
|||
if hasattr(skill, "config"):
|
||||
try:
|
||||
config = skill.config.to_dict() if hasattr(skill.config, "to_dict") else {}
|
||||
except Exception:
|
||||
except (AttributeError, ValueError, TypeError):
|
||||
config = {}
|
||||
|
||||
return {
|
||||
|
|
@ -174,7 +174,7 @@ async def get_skill_detail(skill_name: str, req: Request):
|
|||
skill_registry = req.app.state.skill_registry
|
||||
try:
|
||||
skill = skill_registry.get(skill_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
raise HTTPException(status_code=404, detail=f"Skill '{skill_name}' not found")
|
||||
|
||||
return _skill_to_detail(skill)
|
||||
|
|
@ -186,7 +186,7 @@ async def check_skill_health(skill_name: str, req: Request):
|
|||
skill_registry = req.app.state.skill_registry
|
||||
try:
|
||||
skill_registry.get(skill_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
raise HTTPException(status_code=404, detail=f"Skill '{skill_name}' not found")
|
||||
|
||||
# Basic health check - skill exists and is registered
|
||||
|
|
@ -243,7 +243,7 @@ async def reload_skill(skill_name: str, req: Request):
|
|||
# Verify the skill is currently registered (404 if not).
|
||||
try:
|
||||
skill_registry.get(skill_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
raise HTTPException(status_code=404, detail=f"Skill '{skill_name}' not found")
|
||||
|
||||
# Resolve the skills directory (mirrors routes.skills._get_skills_dir).
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ async def register_skill(request: RegisterSkillRequest, req: Request):
|
|||
|
||||
try:
|
||||
config = SkillConfig.from_dict(request.config)
|
||||
except Exception as e:
|
||||
except (ValueError, TypeError, KeyError) as e:
|
||||
raise HTTPException(status_code=422, detail=f"Invalid skill config: {e}")
|
||||
|
||||
skill = Skill(config=config)
|
||||
|
|
@ -279,7 +279,7 @@ async def install_skill(request: InstallSkillRequest, req: Request):
|
|||
resp = await client.get(source)
|
||||
resp.raise_for_status()
|
||||
yaml_content = resp.text
|
||||
except Exception as e:
|
||||
except (httpx.HTTPError, OSError) as e:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to download from source: {e}")
|
||||
elif source and source.startswith("file://"):
|
||||
# Read from local file path
|
||||
|
|
@ -295,7 +295,7 @@ async def install_skill(request: InstallSkillRequest, req: Request):
|
|||
try:
|
||||
with open(local_path, encoding="utf-8") as f:
|
||||
yaml_content = f.read()
|
||||
except Exception as e:
|
||||
except OSError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to read local file: {e}")
|
||||
else:
|
||||
# Search GitHub for skills (YAML config files)
|
||||
|
|
@ -313,7 +313,7 @@ async def install_skill(request: InstallSkillRequest, req: Request):
|
|||
},
|
||||
)
|
||||
gh_data = gh_resp.json()
|
||||
except Exception as e:
|
||||
except (httpx.HTTPError, OSError, ValueError) as e:
|
||||
raise HTTPException(status_code=502, detail=f"GitHub search failed: {e}")
|
||||
|
||||
items = gh_data.get("items", [])
|
||||
|
|
@ -334,7 +334,7 @@ async def install_skill(request: InstallSkillRequest, req: Request):
|
|||
},
|
||||
)
|
||||
items = gh_resp2.json().get("items", [])
|
||||
except Exception:
|
||||
except (httpx.HTTPError, OSError, ValueError, KeyError):
|
||||
items = []
|
||||
|
||||
if not items:
|
||||
|
|
@ -362,7 +362,7 @@ async def install_skill(request: InstallSkillRequest, req: Request):
|
|||
resp = await client.get(raw_url)
|
||||
resp.raise_for_status()
|
||||
yaml_content = resp.text
|
||||
except Exception as e:
|
||||
except (httpx.HTTPError, OSError) as e:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to download skill: {e}")
|
||||
|
||||
# Validate YAML content before writing to disk
|
||||
|
|
@ -391,14 +391,14 @@ async def install_skill(request: InstallSkillRequest, req: Request):
|
|||
)
|
||||
loader.load_from_file(file_path)
|
||||
registration_ok = True
|
||||
except Exception as e:
|
||||
except (ValueError, TypeError, KeyError, OSError, RuntimeError) as e:
|
||||
logger.warning(f"Failed to register installed skill: {e}")
|
||||
|
||||
if not registration_ok:
|
||||
# Remove the invalid YAML file and report error
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except Exception:
|
||||
except OSError:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail="Skill downloaded but registration failed")
|
||||
|
||||
|
|
@ -419,7 +419,7 @@ async def uninstall_skill(name: str, req: Request):
|
|||
|
||||
try:
|
||||
skill_registry.get(validated_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, RuntimeError):
|
||||
raise HTTPException(status_code=404, detail=f"Skill '{name}' not found")
|
||||
|
||||
# Remove from registry
|
||||
|
|
@ -487,7 +487,7 @@ async def execute_pipeline(name: str, request: ExecutePipelineRequest, req: Requ
|
|||
|
||||
try:
|
||||
result = await pipeline.execute(input_data=request.input_data)
|
||||
except Exception as e:
|
||||
except (ValueError, TypeError, KeyError, RuntimeError, OSError, ConnectionError) as e:
|
||||
logger.error(f"Pipeline execution failed for '{name}': {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Pipeline execution failed")
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def _read_meminfo() -> dict[str, int]:
|
|||
key = parts[0].strip()
|
||||
value = parts[1].strip().split()[0]
|
||||
values[key] = int(value) * 1024 # kB -> bytes
|
||||
except Exception as exc:
|
||||
except (OSError, ValueError, FileNotFoundError) as exc:
|
||||
logger.debug(f"Failed to read /proc/meminfo: {exc}")
|
||||
return values
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ async def get_system_resources() -> dict[str, Any]:
|
|||
if hasattr(os, "getloadavg"):
|
||||
try:
|
||||
loadavg = list(os.getloadavg())
|
||||
except Exception as exc:
|
||||
except (OSError, AttributeError) as exc:
|
||||
logger.debug(f"Failed to get loadavg: {exc}")
|
||||
|
||||
meminfo = _read_meminfo()
|
||||
|
|
@ -68,7 +68,7 @@ async def get_system_resources() -> dict[str, Any]:
|
|||
disk_total = du.total
|
||||
disk_used = du.used
|
||||
disk_free = du.free
|
||||
except Exception as exc:
|
||||
except (OSError, FileNotFoundError) as exc:
|
||||
logger.debug(f"Failed to get disk usage: {exc}")
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ async def submit_task(request: SubmitTaskRequest, req: Request):
|
|||
elif request.skill_name:
|
||||
try:
|
||||
skill = skill_registry.get(request.skill_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Skill '{request.skill_name}' not found",
|
||||
|
|
@ -145,7 +145,7 @@ async def submit_task(request: SubmitTaskRequest, req: Request):
|
|||
quality_result = await quality_gate.validate(
|
||||
task_result.output_data or {}, skill, skill_context=skill_context
|
||||
)
|
||||
except Exception:
|
||||
except (RuntimeError, ValueError, KeyError, AttributeError, asyncio.TimeoutError):
|
||||
pass # Quality gate failure shouldn't block the response
|
||||
|
||||
# 7. Standardize output if skill available
|
||||
|
|
@ -167,7 +167,7 @@ async def submit_task(request: SubmitTaskRequest, req: Request):
|
|||
"task_id": task.task_id,
|
||||
"status": task_result.status,
|
||||
}
|
||||
except Exception:
|
||||
except (ValueError, KeyError, AttributeError, RuntimeError):
|
||||
pass # Fall through to raw output
|
||||
|
||||
# 8. Return raw result if no skill or standardization failed
|
||||
|
|
@ -307,7 +307,7 @@ async def resume_task(task_id: str, req: Request, plan_id: str | None = None):
|
|||
finally:
|
||||
try:
|
||||
await team.dissolve()
|
||||
except Exception:
|
||||
except (RuntimeError, asyncio.TimeoutError, ConnectionError):
|
||||
pass
|
||||
|
||||
return {
|
||||
|
|
@ -343,7 +343,7 @@ async def stream_task(request: SubmitTaskRequest, req: Request):
|
|||
elif request.skill_name:
|
||||
try:
|
||||
skill_registry.get(request.skill_name)
|
||||
except Exception:
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Skill '{request.skill_name}' not found",
|
||||
|
|
|
|||
|
|
@ -359,14 +359,14 @@ async def execute_command(
|
|||
await check_pty.close()
|
||||
if cwd_result.exit_code == 0:
|
||||
state.cwd = cwd_result.output.strip()
|
||||
except Exception:
|
||||
except (OSError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
output = f"命令执行超时({request.timeout}s)"
|
||||
exit_code = -1
|
||||
duration_ms = int((time.monotonic() - start_time) * 1000)
|
||||
except Exception as e:
|
||||
except (OSError, RuntimeError, ValueError) as e:
|
||||
output = str(e)
|
||||
exit_code = -1
|
||||
duration_ms = int((time.monotonic() - start_time) * 1000)
|
||||
|
|
@ -515,7 +515,7 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
})
|
||||
await websocket.close(code=4003, reason="Permission denied")
|
||||
return
|
||||
except Exception:
|
||||
except (ValueError, KeyError, RuntimeError, OSError):
|
||||
pass # Fall through to API key / dev mode
|
||||
|
||||
# 2. API key via ?api_key=
|
||||
|
|
@ -721,7 +721,7 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
})
|
||||
except asyncio.TimeoutError:
|
||||
continue
|
||||
except Exception:
|
||||
except (OSError, RuntimeError):
|
||||
break
|
||||
|
||||
# Get final result
|
||||
|
|
@ -741,7 +741,7 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
await check_pty.close()
|
||||
if cwd_result.exit_code == 0:
|
||||
state.cwd = cwd_result.output.strip()
|
||||
except Exception:
|
||||
except (OSError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
|
||||
# Record history
|
||||
|
|
@ -790,6 +790,8 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
"cwd": state.cwd,
|
||||
"duration_ms": duration_ms,
|
||||
})
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
duration_ms = int((time.monotonic() - start_time) * 1000)
|
||||
await websocket.send_json({
|
||||
|
|
@ -800,7 +802,7 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
if active_pty and active_pty.is_running:
|
||||
try:
|
||||
await active_pty.close()
|
||||
except Exception:
|
||||
except (OSError, RuntimeError):
|
||||
pass
|
||||
active_pty = None
|
||||
|
||||
|
|
@ -836,11 +838,13 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
|
||||
except WebSocketDisconnect:
|
||||
logger.debug(f"Terminal WebSocket disconnected for session {state.session_id}")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Terminal WebSocket error for session {state.session_id}: {e}")
|
||||
try:
|
||||
await websocket.send_json({"type": "error", "message": str(e)[:200]})
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
finally:
|
||||
# Clean up pending confirmations
|
||||
|
|
@ -851,5 +855,5 @@ async def terminal_websocket(websocket: WebSocket) -> None:
|
|||
if active_pty and active_pty.is_running:
|
||||
try:
|
||||
await active_pty.close()
|
||||
except Exception:
|
||||
except (OSError, RuntimeError):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
})
|
||||
await websocket.close(code=4003, reason="Permission denied")
|
||||
return
|
||||
except Exception:
|
||||
except (ValueError, KeyError, RuntimeError, OSError):
|
||||
pass
|
||||
|
||||
if not auth_ok:
|
||||
|
|
@ -585,7 +585,7 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
})
|
||||
await websocket.close(code=4003, reason="Not authorized")
|
||||
return
|
||||
except Exception as e:
|
||||
except (aiosqlite.Error, OSError, ValueError, KeyError, RuntimeError) as e:
|
||||
logger.warning(f"Failed to check server terminal authorization: {e}")
|
||||
# If DB check fails, deny access
|
||||
await websocket.send_json({
|
||||
|
|
@ -822,7 +822,7 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
})
|
||||
except asyncio.TimeoutError:
|
||||
continue
|
||||
except Exception:
|
||||
except (OSError, RuntimeError):
|
||||
break
|
||||
|
||||
result = await run_task
|
||||
|
|
@ -846,7 +846,7 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
else:
|
||||
base = state.cwd or os.path.expanduser("~")
|
||||
state.cwd = os.path.normpath(os.path.join(base, cd_arg))
|
||||
except Exception:
|
||||
except (ValueError, OSError, RuntimeError):
|
||||
pass # cd parsing failed — keep old cwd
|
||||
|
||||
# Record history
|
||||
|
|
@ -893,6 +893,8 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
"cwd": state.cwd,
|
||||
"duration_ms": duration_ms,
|
||||
})
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
duration_ms = int((time.monotonic() - start_time) * 1000)
|
||||
await websocket.send_json({
|
||||
|
|
@ -903,7 +905,7 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
if active_pty and active_pty.is_running:
|
||||
try:
|
||||
await active_pty.close()
|
||||
except Exception:
|
||||
except (OSError, RuntimeError):
|
||||
pass
|
||||
active_pty = None
|
||||
|
||||
|
|
@ -942,17 +944,19 @@ async def server_terminal_websocket(websocket: WebSocket) -> None:
|
|||
|
||||
except WebSocketDisconnect:
|
||||
logger.debug(f"Server terminal WebSocket disconnected for session {state.session_id}")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Server terminal WebSocket error for session {state.session_id}: {e}")
|
||||
try:
|
||||
await websocket.send_json({"type": "error", "message": str(e)[:200]})
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
finally:
|
||||
if active_pty and active_pty.is_running:
|
||||
try:
|
||||
await active_pty.close()
|
||||
except Exception:
|
||||
except (OSError, RuntimeError):
|
||||
pass
|
||||
# Clean up session from in-memory stores to prevent leaks
|
||||
_server_sessions.pop(state.session_id, None)
|
||||
|
|
|
|||
|
|
@ -433,6 +433,8 @@ async def _execute_workflow(
|
|||
)
|
||||
result = await agent.handle_task(task)
|
||||
stage_result = {"output": result, "skill": stage.action}
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
stage_result = {"error": str(e), "skill": stage.action}
|
||||
else:
|
||||
|
|
@ -474,6 +476,8 @@ async def _execute_workflow(
|
|||
)
|
||||
result = await agent.handle_task(task)
|
||||
return {"output": result, "skill": action}
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
return {"error": str(e), "skill": action}
|
||||
return {"dry_run": True, "action": action}
|
||||
|
|
@ -515,6 +519,8 @@ async def _execute_workflow(
|
|||
execution_id=execution.execution_id,
|
||||
)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
execution.stage_results[stage_name] = {
|
||||
"status": "failed",
|
||||
|
|
@ -633,7 +639,7 @@ async def _broadcast_ws(message: dict[str, Any], execution_id: str | None = None
|
|||
for ws in targets:
|
||||
try:
|
||||
await ws.send_json(message)
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
disconnected.append(ws)
|
||||
if disconnected:
|
||||
async with _ws_subscribers_lock:
|
||||
|
|
@ -952,7 +958,7 @@ async def workflow_websocket(websocket: WebSocket):
|
|||
# Keep connection alive - messages are primarily server-push
|
||||
except WebSocketDisconnect:
|
||||
logger.debug("Workflow WebSocket disconnected")
|
||||
except Exception as e:
|
||||
except (RuntimeError, asyncio.TimeoutError, ConnectionError) as e:
|
||||
logger.error(f"Workflow WebSocket error: {e}")
|
||||
finally:
|
||||
if subscribed_execution_id:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class ConnectionManager:
|
|||
for ws, _ in conns:
|
||||
try:
|
||||
await ws.send_json(message)
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
stale.append(ws)
|
||||
for ws in stale:
|
||||
self.remove(task_id, ws)
|
||||
|
|
@ -153,6 +153,8 @@ async def task_websocket(websocket: WebSocket, task_id: str) -> None:
|
|||
|
||||
except WebSocketDisconnect:
|
||||
logger.debug(f"WebSocket disconnected for task {task_id}")
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket error for task {task_id}: {e}")
|
||||
try:
|
||||
|
|
@ -162,7 +164,7 @@ async def task_websocket(websocket: WebSocket, task_id: str) -> None:
|
|||
"data": {"message": str(e)},
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
except (ConnectionError, RuntimeError, asyncio.TimeoutError):
|
||||
pass
|
||||
finally:
|
||||
manager.remove(task_id, websocket)
|
||||
|
|
@ -243,6 +245,8 @@ async def _run_react_and_stream(
|
|||
},
|
||||
)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
await websocket.send_json(
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue