diff --git a/ai-hub/app/api/routes/agents.py b/ai-hub/app/api/routes/agents.py index 36ed7fc..324e671 100644 --- a/ai-hub/app/api/routes/agents.py +++ b/ai-hub/app/api/routes/agents.py @@ -1,5 +1,5 @@ -from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Response, status -from typing import List +from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Response, status, Query +from typing import List, Optional, Annotated from sqlalchemy.orm import Session from app.api.dependencies import ServiceContainer, get_db, get_current_user from app.api import schemas @@ -18,6 +18,8 @@ from sqlalchemy.orm import joinedload +logger = logging.getLogger(__name__) + def create_agents_router(services: ServiceContainer) -> APIRouter: router = APIRouter() @@ -46,7 +48,17 @@ return services.agent_service.update_config(db, id, current_user.id, request) @router.post("/{id}/webhook") - async def webhook_receiver(id: str, payload: dict, background_tasks: BackgroundTasks, response: Response, token: str = None, sync: bool = False, skip_coworker: bool = False, db: Session = Depends(get_db)): + async def webhook_receiver( + id: str, + payload: dict, + background_tasks: BackgroundTasks, + response: Response, + token: Annotated[Optional[str], Query()] = None, + sync: bool = False, + skip_coworker: bool = False, + db: Session = Depends(get_db) + ): + logger.info(f"[Webhook] Trigger received for agent {id} (token provided: {bool(token)})") instance = services.agent_service.validate_webhook_trigger(db, id, token) # Extract prompt from payload (supports 'prompt' or legacy 'override_prompt') @@ -65,18 +77,31 @@ return {"status": "accepted", "message": "Background task initiated"} @router.post("/{id}/run", status_code=202) - def manual_trigger(id: str, payload: dict, background_tasks: BackgroundTasks, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): - instance = db.query(AgentInstance).filter( - AgentInstance.id == id, - AgentInstance.user_id == current_user.id - ).first() - if not instance: - raise HTTPException(status_code=404, detail="Instance not found") + async def manual_trigger(id: str, payload: dict, background_tasks: BackgroundTasks, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): + try: + logger.info(f"[ManualTrigger] Agent {id} manual run initiated by user {current_user.id}") + logger.debug(f"[ManualTrigger] Payload: {json.dumps(payload)}") - prompt = payload.get("prompt") or f"Manual triggered execution for agent {id}." - should_skip = payload.get("skip_coworker", False) - background_tasks.add_task(AgentExecutor.run, instance.id, prompt, services, services.user_service, should_skip) - return {"message": "Accepted"} + instance = db.query(AgentInstance).filter( + AgentInstance.id == id, + AgentInstance.user_id == current_user.id + ).first() + + if not instance: + logger.warning(f"[ManualTrigger] Instance {id} not found for user {current_user.id}") + raise HTTPException(status_code=404, detail="Instance not found") + + prompt = payload.get("prompt") or f"Manual triggered execution for agent {id}." + should_skip = payload.get("skip_coworker", False) + + logger.info(f"[ManualTrigger] Scheduling AgentExecutor for {id}") + background_tasks.add_task(AgentExecutor.run, instance.id, prompt, services, services.user_service, should_skip) + return {"message": "Accepted"} + except HTTPException: + raise + except Exception as e: + logger.error(f"[ManualTrigger] Internal error: {e}") + raise HTTPException(status_code=500, detail=f"Failed to initiate trigger: {str(e)}") @router.get("/{id}/triggers", response_model=List[schemas.AgentTriggerResponse]) def get_agent_triggers(id: str, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): diff --git a/ai-hub/app/core/orchestration/agent_loop.py b/ai-hub/app/core/orchestration/agent_loop.py index b959d31..548051a 100644 --- a/ai-hub/app/core/orchestration/agent_loop.py +++ b/ai-hub/app/core/orchestration/agent_loop.py @@ -33,9 +33,12 @@ @staticmethod async def run(agent_id: str, prompt: str, services, user_service, skip_coworker: bool = False): """Entry point for the background execution task.""" + logger.info(f"[AgentExecutor] Starting background run for agent {agent_id}") executor = AgentExecutor(agent_id, services, user_service) try: - return await executor.execute(prompt, skip_coworker) + result = await executor.execute(prompt, skip_coworker) + logger.info(f"[AgentExecutor] Completed background run for agent {agent_id}") + return result finally: executor.db.close() diff --git a/ai-hub/app/core/skills/fs_loader.py b/ai-hub/app/core/skills/fs_loader.py index 8953c39..12b674f 100644 --- a/ai-hub/app/core/skills/fs_loader.py +++ b/ai-hub/app/core/skills/fs_loader.py @@ -11,6 +11,7 @@ class FileSystemSkillLoader: def __init__(self, base_dirs: List[str]): self.base_dirs = base_dirs + self._non_writable_dirs = set() def _ensure_metadata(self, folder_path: str, skill_name: str, is_system: bool = False): """ @@ -19,6 +20,13 @@ """ meta_path = os.path.join(folder_path, ".metadata.json") if not os.path.exists(meta_path): + if folder_path in self._non_writable_dirs: + return { + "owner_id": "admin", + "is_system": is_system, + "extra_metadata": {"emoji": "🛠️"} + } + default_metadata = { "owner_id": "admin", "is_system": is_system, @@ -29,7 +37,8 @@ json.dump(default_metadata, f, indent=4) logger.info(f"Generated default .metadata.json for skill '{skill_name}'") except Exception as e: - logger.warning(f"Could not generate metadata for {skill_name} (likely read-only FS): {e}") + self._non_writable_dirs.add(folder_path) + logger.warning(f"Could not generate metadata for {skill_name} (cached as Read-Only): {e}") return default_metadata try: diff --git a/docker-compose.yml b/docker-compose.yml index e68d162..be57271 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,7 @@ - ./config.yaml:/app/config.yaml:rw - ./ai-hub/app:/app/app:rw - ./agent-node:/app/agent-node-source:ro - - ./skills:/app/skills:ro + - ./skills:/app/skills:rw - ./docs:/app/docs:ro - ./blueprints:/app/blueprints:ro - browser_shm:/dev/shm:rw diff --git a/frontend/src/features/agents/hooks/useAgentDrillDown.js b/frontend/src/features/agents/hooks/useAgentDrillDown.js index 92b9435..db3e21a 100644 --- a/frontend/src/features/agents/hooks/useAgentDrillDown.js +++ b/frontend/src/features/agents/hooks/useAgentDrillDown.js @@ -491,14 +491,19 @@ }; const handleInjectOverride = async (e) => { - e.preventDefault(); - if (!overrideText.trim() || !agent?.session_id) return; + if (e) e.preventDefault(); + if (!overrideText.trim()) return; try { await fetchWithAuth(`/agents/${agentId}/run`, { method: "POST", body: { prompt: overrideText } }); + setModalConfig({ + title: 'Task Injected', + message: 'Manual task override has been sent to the agent.', + type: 'success' + }); setOverrideText(""); fetchData(); } catch (err) {