diff --git a/src/agentkit/server/routes/agents.py b/src/agentkit/server/routes/agents.py index 9e77e72..5f39ac6 100644 --- a/src/agentkit/server/routes/agents.py +++ b/src/agentkit/server/routes/agents.py @@ -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: diff --git a/src/agentkit/server/routes/auth.py b/src/agentkit/server/routes/auth.py index aa16d42..88a6c87 100644 --- a/src/agentkit/server/routes/auth.py +++ b/src/agentkit/server/routes/auth.py @@ -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 diff --git a/src/agentkit/server/routes/bitable.py b/src/agentkit/server/routes/bitable.py index 64a11d6..d8e8f95 100644 --- a/src/agentkit/server/routes/bitable.py +++ b/src/agentkit/server/routes/bitable.py @@ -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 diff --git a/src/agentkit/server/routes/channels.py b/src/agentkit/server/routes/channels.py index 8b6033f..f8ecda5 100644 --- a/src/agentkit/server/routes/channels.py +++ b/src/agentkit/server/routes/channels.py @@ -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"} diff --git a/src/agentkit/server/routes/chat.py b/src/agentkit/server/routes/chat.py index f47b5a7..ae81da5 100644 --- a/src/agentkit/server/routes/chat.py +++ b/src/agentkit/server/routes/chat.py @@ -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 diff --git a/src/agentkit/server/routes/config_sync.py b/src/agentkit/server/routes/config_sync.py index 50b95b3..8231891 100644 --- a/src/agentkit/server/routes/config_sync.py +++ b/src/agentkit/server/routes/config_sync.py @@ -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 diff --git a/src/agentkit/server/routes/documents.py b/src/agentkit/server/routes/documents.py index 0282f58..554c924 100644 --- a/src/agentkit/server/routes/documents.py +++ b/src/agentkit/server/routes/documents.py @@ -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: diff --git a/src/agentkit/server/routes/evolution.py b/src/agentkit/server/routes/evolution.py index 6db3930..10ff964 100644 --- a/src/agentkit/server/routes/evolution.py +++ b/src/agentkit/server/routes/evolution.py @@ -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") diff --git a/src/agentkit/server/routes/evolution_dashboard.py b/src/agentkit/server/routes/evolution_dashboard.py index 2c491fb..4168f73 100644 --- a/src/agentkit/server/routes/evolution_dashboard.py +++ b/src/agentkit/server/routes/evolution_dashboard.py @@ -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: diff --git a/src/agentkit/server/routes/health.py b/src/agentkit/server/routes/health.py index 06b3fe6..64e2ba9 100644 --- a/src/agentkit/server/routes/health.py +++ b/src/agentkit/server/routes/health.py @@ -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", diff --git a/src/agentkit/server/routes/kb_management.py b/src/agentkit/server/routes/kb_management.py index 1bc570d..4644828 100644 --- a/src/agentkit/server/routes/kb_management.py +++ b/src/agentkit/server/routes/kb_management.py @@ -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 diff --git a/src/agentkit/server/routes/mcp_publish.py b/src/agentkit/server/routes/mcp_publish.py index 25397c0..1d9b074 100644 --- a/src/agentkit/server/routes/mcp_publish.py +++ b/src/agentkit/server/routes/mcp_publish.py @@ -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: diff --git a/src/agentkit/server/routes/memory.py b/src/agentkit/server/routes/memory.py index 7863a5f..8e6f098 100644 --- a/src/agentkit/server/routes/memory.py +++ b/src/agentkit/server/routes/memory.py @@ -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") diff --git a/src/agentkit/server/routes/metrics.py b/src/agentkit/server/routes/metrics.py index 451002b..b9eb8fd 100644 --- a/src/agentkit/server/routes/metrics.py +++ b/src/agentkit/server/routes/metrics.py @@ -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 { diff --git a/src/agentkit/server/routes/portal.py b/src/agentkit/server/routes/portal.py index 80e7f1d..76b597c 100644 --- a/src/agentkit/server/routes/portal.py +++ b/src/agentkit/server/routes/portal.py @@ -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. diff --git a/src/agentkit/server/routes/skill_management.py b/src/agentkit/server/routes/skill_management.py index a998c03..12838bb 100644 --- a/src/agentkit/server/routes/skill_management.py +++ b/src/agentkit/server/routes/skill_management.py @@ -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). diff --git a/src/agentkit/server/routes/skills.py b/src/agentkit/server/routes/skills.py index abb8a6c..e40a5f8 100644 --- a/src/agentkit/server/routes/skills.py +++ b/src/agentkit/server/routes/skills.py @@ -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") diff --git a/src/agentkit/server/routes/system.py b/src/agentkit/server/routes/system.py index d85f8ad..5b7dcaf 100644 --- a/src/agentkit/server/routes/system.py +++ b/src/agentkit/server/routes/system.py @@ -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 { diff --git a/src/agentkit/server/routes/tasks.py b/src/agentkit/server/routes/tasks.py index b274e0e..1e00ca3 100644 --- a/src/agentkit/server/routes/tasks.py +++ b/src/agentkit/server/routes/tasks.py @@ -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", diff --git a/src/agentkit/server/routes/terminal.py b/src/agentkit/server/routes/terminal.py index fe7f876..0577345 100644 --- a/src/agentkit/server/routes/terminal.py +++ b/src/agentkit/server/routes/terminal.py @@ -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 diff --git a/src/agentkit/server/routes/terminal_server.py b/src/agentkit/server/routes/terminal_server.py index 80df81d..05db4ae 100644 --- a/src/agentkit/server/routes/terminal_server.py +++ b/src/agentkit/server/routes/terminal_server.py @@ -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) diff --git a/src/agentkit/server/routes/workflows.py b/src/agentkit/server/routes/workflows.py index a41331f..8676221 100644 --- a/src/agentkit/server/routes/workflows.py +++ b/src/agentkit/server/routes/workflows.py @@ -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: diff --git a/src/agentkit/server/routes/ws.py b/src/agentkit/server/routes/ws.py index 10bd943..338885e 100644 --- a/src/agentkit/server/routes/ws.py +++ b/src/agentkit/server/routes/ws.py @@ -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( {