fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner - P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var - P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup - P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns - P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs) - P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import - P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default - P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed - P2: 28 frontend TODOs (hardcoded constants, deprecated notification API) - P3: dead code, duplicates, deprecated imports, private attr, __import__ inline - P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n) - ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix) - F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String) - Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
This commit is contained in:
@@ -27,8 +27,8 @@ async def send_agent_message(
|
||||
3. Enqueue run_agent for the target agent with the message as trigger_data
|
||||
4. Return delivery status
|
||||
"""
|
||||
from app.plugins.builtins.automation.models import AgentDefinition
|
||||
from app.plugins.builtins.automation.agent_runner import run_agent
|
||||
from app.plugins.builtins.automation.models import AgentDefinition
|
||||
|
||||
# 1. Find target agent by name
|
||||
result = await db.execute(
|
||||
@@ -56,7 +56,6 @@ async def send_agent_message(
|
||||
# 2. Create a kommunikation message in a dedicated agent room
|
||||
try:
|
||||
from app.plugins.builtins.kommunikation.contracts import Message, Room
|
||||
from app.plugins.builtins.kommunikation.contracts import RoomService
|
||||
|
||||
# Find or create the agent-to-agent room
|
||||
room_name = f"agent:{from_agent_id}:{target_agent.id}"
|
||||
|
||||
@@ -12,7 +12,8 @@ import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select, update as sa_update
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import update as sa_update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.plugins.builtins.automation.models import AgentSubtask
|
||||
@@ -60,12 +61,12 @@ class AgentCoordinator:
|
||||
return subtask
|
||||
|
||||
@staticmethod
|
||||
async def wait_for_subtask(
|
||||
async def wait_for_subtask( # noqa: ASYNC109
|
||||
db: AsyncSession,
|
||||
tenant_id: uuid.UUID,
|
||||
subtask_id: uuid.UUID,
|
||||
poll_interval: float = 0.5,
|
||||
timeout: float = 300.0,
|
||||
timeout: float = 300.0, # noqa: ASYNC109
|
||||
) -> dict[str, Any]:
|
||||
"""Wait for a subtask to complete, fail, or be cancelled.
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import UTC
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import get_db, set_tenant_context
|
||||
from app.core.visibility import apply_visibility_filter
|
||||
from app.core.db import get_db
|
||||
from app.deps import get_current_user, require_permission
|
||||
from app.plugins.builtins.automation.models import (
|
||||
AgentDefinition,
|
||||
@@ -216,7 +216,7 @@ async def get_agent(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
agent = await AgentService.get_by_id(db, tenant_id, aid)
|
||||
if agent is None:
|
||||
@@ -241,7 +241,7 @@ async def update_agent(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
agent = await AgentService.update(
|
||||
db, tenant_id, aid, data.model_dump(exclude_none=True), user_id=user_id
|
||||
@@ -265,7 +265,7 @@ async def delete_agent(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
success = await AgentService.delete(db, tenant_id, aid)
|
||||
if not success:
|
||||
@@ -290,7 +290,7 @@ async def execute_agent(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
agent = await AgentService.get_by_id(db, tenant_id, aid)
|
||||
if agent is None:
|
||||
@@ -325,9 +325,10 @@ async def execute_agent(
|
||||
)
|
||||
|
||||
# Update run with results
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import update as sa_update
|
||||
from datetime import datetime, timezone
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
async with db.begin():
|
||||
await db.execute(
|
||||
sa_update(AgentRun)
|
||||
@@ -358,7 +359,7 @@ async def test_run_agent(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
agent = await AgentService.get_by_id(db, tenant_id, aid)
|
||||
if agent is None:
|
||||
@@ -397,7 +398,7 @@ async def list_agent_runs(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
items, total = await RunLogService.list_agent_runs(
|
||||
db, tenant_id, agent_id=aid, status=status, limit=limit, offset=offset
|
||||
@@ -428,7 +429,7 @@ async def list_agent_versions(
|
||||
try:
|
||||
aid = uuid.UUID(agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
items, total = await AgentService.get_versions(
|
||||
db, tenant_id, aid, limit=limit, offset=offset
|
||||
@@ -457,7 +458,7 @@ async def restore_agent_version(
|
||||
aid = uuid.UUID(agent_id)
|
||||
vid = uuid.UUID(version_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid ID") from None
|
||||
|
||||
agent = await AgentService.restore_version(
|
||||
db, tenant_id, aid, vid, user_id=user_id
|
||||
@@ -485,7 +486,7 @@ async def send_agent_message_endpoint(
|
||||
try:
|
||||
aid = uuid.UUID(id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
# Verify the source agent exists
|
||||
agent = await AgentService.get_by_id(db, tenant_id, aid)
|
||||
|
||||
@@ -16,8 +16,8 @@ from typing import Any
|
||||
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.core.db import get_session_factory
|
||||
from app.ai.llm_client import llm_complete
|
||||
from app.core.db import get_session_factory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -161,15 +161,6 @@ async def run_agent(
|
||||
try:
|
||||
async def _run_llm() -> None:
|
||||
"""Inner coroutine for LLM call with tool execution."""
|
||||
from app.ai.llm_client import LLMClient
|
||||
|
||||
llm = LLMClient(
|
||||
model=agent.model or None,
|
||||
api_key=agent.api_key or None,
|
||||
api_base=agent.api_base or None,
|
||||
provider=agent.provider or None,
|
||||
)
|
||||
|
||||
# Build system prompt from agent configuration
|
||||
system_prompt = agent.system_prompt or "You are a helpful AI assistant."
|
||||
user_prompt = f"Context: {context_data}"
|
||||
@@ -241,7 +232,7 @@ async def run_agent(
|
||||
# Run with timeout
|
||||
try:
|
||||
await asyncio.wait_for(_run_llm(), timeout=max_duration)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
"Agent %s execution timed out after %d seconds",
|
||||
agent.id, max_duration,
|
||||
@@ -295,6 +286,6 @@ async def run_agent(
|
||||
|
||||
|
||||
# Register all job functions with the job registry
|
||||
from app.core.job_registry import register_job
|
||||
from app.core.job_registry import register_job # noqa: E402
|
||||
|
||||
register_job("run_agent", run_agent)
|
||||
register_job("run_agent", run_agent)
|
||||
|
||||
@@ -5,7 +5,9 @@ Exposes models, services, scheduler, and agent communication for other plugins.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
from app.plugins.builtins.automation.agent_comm import send_agent_message
|
||||
from app.plugins.builtins.automation.agent_runner import run_agent
|
||||
from app.plugins.builtins.automation.execution_engine import run_automation
|
||||
from app.plugins.builtins.automation.models import (
|
||||
AgentDefinition,
|
||||
AgentRun,
|
||||
@@ -15,19 +17,17 @@ from app.plugins.builtins.automation.models import (
|
||||
AutomationRun,
|
||||
AutomationVersion,
|
||||
)
|
||||
from app.plugins.builtins.automation.scheduler import (
|
||||
calculate_next_run,
|
||||
scheduler_tick,
|
||||
)
|
||||
from app.plugins.builtins.automation.services import (
|
||||
AgentService,
|
||||
AutomationService,
|
||||
CronJobService,
|
||||
RunLogService,
|
||||
)
|
||||
from app.plugins.builtins.automation.agent_runner import run_agent
|
||||
from app.plugins.builtins.automation.execution_engine import run_automation
|
||||
from app.plugins.builtins.automation.scheduler import (
|
||||
calculate_next_run,
|
||||
scheduler_tick,
|
||||
)
|
||||
from app.plugins.builtins.automation.agent_comm import send_agent_message
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
|
||||
|
||||
class AutomationContract:
|
||||
@@ -73,8 +73,6 @@ get_contract_registry().register("automation", _contract)
|
||||
__all__ = [
|
||||
"AutomationContract",
|
||||
"AgentDefinition",
|
||||
"Automation",
|
||||
"CronJob",
|
||||
"AgentService",
|
||||
"AutomationService",
|
||||
"CronJobService",
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
@@ -166,9 +164,10 @@ async def _execute_action(
|
||||
return result
|
||||
|
||||
try:
|
||||
from app.services.workflow_service import create_instance
|
||||
from uuid import UUID
|
||||
|
||||
from app.services.workflow_service import create_instance
|
||||
|
||||
factory = get_session_factory()
|
||||
async with factory() as db:
|
||||
instance = await create_instance(
|
||||
@@ -280,6 +279,6 @@ async def run_automation(
|
||||
|
||||
|
||||
# Register all job functions with the job registry
|
||||
from app.core.job_registry import register_job
|
||||
from app.core.job_registry import register_job # noqa: E402
|
||||
|
||||
register_job("run_automation", run_automation)
|
||||
|
||||
@@ -16,9 +16,10 @@ async def backup_check(ctx: dict[str, Any]) -> None:
|
||||
Runs daily at 2:00. Checks the last backup timestamp from system settings
|
||||
and publishes backup.completed or backup.failed events accordingly.
|
||||
"""
|
||||
from app.core.event_bus import get_event_bus
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.core.event_bus import get_event_bus
|
||||
|
||||
event_bus = get_event_bus()
|
||||
factory = get_session_factory()
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ from sqlalchemy import (
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.db import Base, TenantMixin
|
||||
|
||||
@@ -176,6 +176,18 @@ class AutomationPlugin(BasePlugin):
|
||||
self._contributed_cron_jobs: dict[str, list[str]] = {} # plugin_name -> [cron_job_name, ...]
|
||||
self._contributed_heartbeats: dict[str, list[str]] = {} # plugin_name -> [agent_name, ...]
|
||||
|
||||
def get_entity_models(self) -> dict[str, type]:
|
||||
from app.plugins.builtins.automation.models import AgentDefinition, AutomationDefinition
|
||||
return {"agent_definition": AgentDefinition, "automation_definition": AutomationDefinition}
|
||||
|
||||
def get_job_modules(self) -> list[str]:
|
||||
return [
|
||||
"app.plugins.builtins.automation.scheduler",
|
||||
"app.plugins.builtins.automation.workflow_timeout",
|
||||
"app.plugins.builtins.automation.agent_runner",
|
||||
"app.plugins.builtins.automation.execution_engine",
|
||||
]
|
||||
|
||||
async def on_activate(self, db, service_container, event_bus) -> None:
|
||||
"""Register event listeners on activation."""
|
||||
await super().on_activate(db, service_container, event_bus)
|
||||
@@ -187,7 +199,9 @@ class AutomationPlugin(BasePlugin):
|
||||
logger.exception("Failed to register agent communication tool")
|
||||
# Register agent coordinator tools
|
||||
try:
|
||||
from app.plugins.builtins.automation.agent_coordinator import register_agent_coordinator_tools
|
||||
from app.plugins.builtins.automation.agent_coordinator import (
|
||||
register_agent_coordinator_tools,
|
||||
)
|
||||
register_agent_coordinator_tools()
|
||||
except Exception:
|
||||
logger.exception("Failed to register agent coordinator tools")
|
||||
@@ -230,7 +244,9 @@ class AutomationPlugin(BasePlugin):
|
||||
logger.exception("Failed to unregister agent communication tool")
|
||||
# Unregister agent coordinator tools
|
||||
try:
|
||||
from app.plugins.builtins.automation.agent_coordinator import unregister_agent_coordinator_tools
|
||||
from app.plugins.builtins.automation.agent_coordinator import (
|
||||
unregister_agent_coordinator_tools,
|
||||
)
|
||||
unregister_agent_coordinator_tools()
|
||||
except Exception:
|
||||
logger.exception("Failed to unregister agent coordinator tools")
|
||||
@@ -249,12 +265,16 @@ class AutomationPlugin(BasePlugin):
|
||||
async def register_plugin_contributions(self, db, plugin_name: str, manifest) -> None:
|
||||
"""Register agent definitions, automation templates, cron jobs, and heartbeat configs
|
||||
from another plugin's manifest. Uses plugin name prefixing for conflict resolution."""
|
||||
from app.plugins.builtins.automation.services import AgentService, AutomationService, CronJobService
|
||||
from app.plugins.builtins.automation.models import AutomationCronJob
|
||||
from sqlalchemy import select
|
||||
|
||||
# Get default tenant_id from the first tenant in the DB
|
||||
from app.models.tenant import Tenant
|
||||
from app.plugins.builtins.automation.models import AutomationCronJob
|
||||
from app.plugins.builtins.automation.services import (
|
||||
AgentService,
|
||||
AutomationService,
|
||||
CronJobService,
|
||||
)
|
||||
tenant_result = await db.execute(select(Tenant).limit(1))
|
||||
tenant = tenant_result.scalar_one_or_none()
|
||||
default_tenant_id = tenant.id if tenant else None
|
||||
@@ -356,7 +376,11 @@ class AutomationPlugin(BasePlugin):
|
||||
|
||||
async def unregister_plugin_contributions(self, db, plugin_name: str) -> None:
|
||||
"""Remove all contributed definitions from a plugin that is being deactivated."""
|
||||
from app.plugins.builtins.automation.services import AgentService, AutomationService, CronJobService
|
||||
from app.plugins.builtins.automation.services import (
|
||||
AgentService,
|
||||
AutomationService,
|
||||
CronJobService,
|
||||
)
|
||||
|
||||
# Remove contributed agents
|
||||
agent_names = self._contributed_agents.pop(plugin_name, [])
|
||||
@@ -384,8 +408,9 @@ class AutomationPlugin(BasePlugin):
|
||||
cron_job_names = self._contributed_cron_jobs.pop(plugin_name, [])
|
||||
for cron_name in cron_job_names:
|
||||
try:
|
||||
from app.plugins.builtins.automation.models import AutomationCronJob
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.plugins.builtins.automation.models import AutomationCronJob
|
||||
result = await db.execute(
|
||||
select(AutomationCronJob).where(AutomationCronJob.name == cron_name).limit(1)
|
||||
)
|
||||
@@ -411,9 +436,10 @@ class AutomationPlugin(BasePlugin):
|
||||
|
||||
async def ensure_ai_proactive_heartbeat(self, db) -> None:
|
||||
"""Migrate the hardcoded ai_proactive heartbeat to a configurable cron job."""
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.plugins.builtins.automation.models import AutomationCronJob
|
||||
from app.plugins.builtins.automation.services import CronJobService
|
||||
from sqlalchemy import select
|
||||
|
||||
# Check if ai_proactive heartbeat cron job already exists
|
||||
result = await db.execute(
|
||||
@@ -456,4 +482,4 @@ class AutomationPlugin(BasePlugin):
|
||||
|
||||
async def on_workflow_timeout(self, payload: dict[str, Any]) -> None:
|
||||
"""Handle workflow.timeout event — trigger matching automations."""
|
||||
logger.debug("workflow.timeout event received: %s", payload)
|
||||
logger.debug("workflow.timeout event received: %s", payload)
|
||||
|
||||
@@ -7,13 +7,13 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import UTC
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import get_db, set_tenant_context
|
||||
from app.core.visibility import apply_visibility_filter
|
||||
from app.core.db import get_db
|
||||
from app.deps import get_current_user, require_permission
|
||||
from app.plugins.builtins.automation.models import (
|
||||
AutomationDefinition,
|
||||
@@ -253,9 +253,10 @@ async def get_automation_settings(
|
||||
):
|
||||
"""Get automation settings (persisted in system_settings metadata)."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
from app.models.system_settings import SystemSettings
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models.system_settings import SystemSettings
|
||||
|
||||
result = await db.execute(
|
||||
select(SystemSettings).where(SystemSettings.tenant_id == tenant_id)
|
||||
)
|
||||
@@ -285,9 +286,9 @@ async def update_automation_settings(
|
||||
):
|
||||
"""Update automation settings (persisted in system_settings metadata)."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
from app.models.system_settings import SystemSettings
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from app.models.system_settings import SystemSettings
|
||||
|
||||
result = await db.execute(
|
||||
select(SystemSettings).where(SystemSettings.tenant_id == tenant_id)
|
||||
@@ -345,7 +346,7 @@ async def get_automation(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
automation = await AutomationService.get_by_id(db, tenant_id, aid)
|
||||
if automation is None:
|
||||
@@ -370,7 +371,7 @@ async def update_automation(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
automation = await AutomationService.update(
|
||||
db, tenant_id, aid, data.model_dump(exclude_none=True), user_id=user_id
|
||||
@@ -394,7 +395,7 @@ async def delete_automation(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
success = await AutomationService.delete(db, tenant_id, aid)
|
||||
if not success:
|
||||
@@ -419,7 +420,7 @@ async def execute_automation(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
automation = await AutomationService.get_by_id(db, tenant_id, aid)
|
||||
if automation is None:
|
||||
@@ -441,7 +442,6 @@ async def execute_automation(
|
||||
await db.flush()
|
||||
|
||||
# Execute automation via execution engine
|
||||
import asyncio
|
||||
from app.plugins.builtins.automation.execution_engine import run_automation
|
||||
|
||||
run_id = str(run.id)
|
||||
@@ -456,9 +456,10 @@ async def execute_automation(
|
||||
)
|
||||
|
||||
# Update run with results
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import update as sa_update
|
||||
from datetime import datetime, timezone
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
async with db.begin():
|
||||
await db.execute(
|
||||
sa_update(AutomationRun)
|
||||
@@ -489,14 +490,16 @@ async def dry_run_automation(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
automation = await AutomationService.get_by_id(db, tenant_id, aid)
|
||||
if automation is None:
|
||||
raise HTTPException(status_code=404, detail="Automation not found")
|
||||
|
||||
# Execute dry-run via execution engine
|
||||
from app.plugins.builtins.automation.execution_engine import run_automation as execute_automation_engine
|
||||
from app.plugins.builtins.automation.execution_engine import (
|
||||
run_automation as execute_automation_engine,
|
||||
)
|
||||
|
||||
result = await execute_automation_engine(
|
||||
ctx={},
|
||||
@@ -558,7 +561,7 @@ async def list_automation_runs(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
items, total = await RunLogService.list_automation_runs(
|
||||
db, tenant_id, automation_id=aid, status=status, limit=limit, offset=offset
|
||||
@@ -589,7 +592,7 @@ async def list_automation_versions(
|
||||
try:
|
||||
aid = uuid.UUID(automation_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid automation ID") from None
|
||||
|
||||
items, total = await AutomationService.get_versions(
|
||||
db, tenant_id, aid, limit=limit, offset=offset
|
||||
@@ -618,7 +621,7 @@ async def restore_automation_version(
|
||||
aid = uuid.UUID(automation_id)
|
||||
vid = uuid.UUID(version_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid ID") from None
|
||||
|
||||
automation = await AutomationService.restore_version(
|
||||
db, tenant_id, aid, vid, user_id=user_id
|
||||
@@ -653,7 +656,7 @@ async def list_subtasks(
|
||||
parent_id = uuid.UUID(parent_agent_id) if parent_agent_id else None
|
||||
child_id = uuid.UUID(child_agent_id) if child_agent_id else None
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
from app.plugins.builtins.automation.agent_coordinator import AgentCoordinator
|
||||
|
||||
@@ -688,7 +691,7 @@ async def create_subtask(
|
||||
parent_id = uuid.UUID(data.parent_agent_id)
|
||||
child_id = uuid.UUID(data.child_agent_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid agent ID") from None
|
||||
|
||||
from app.plugins.builtins.automation.agent_coordinator import AgentCoordinator
|
||||
|
||||
@@ -718,10 +721,11 @@ async def get_subtask(
|
||||
try:
|
||||
sid = uuid.UUID(subtask_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID") from None
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.plugins.builtins.automation.models import AgentSubtask
|
||||
from sqlalchemy import select
|
||||
|
||||
result = await db.execute(
|
||||
select(AgentSubtask)
|
||||
@@ -751,10 +755,11 @@ async def update_subtask(
|
||||
try:
|
||||
sid = uuid.UUID(subtask_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID") from None
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.plugins.builtins.automation.models import AgentSubtask
|
||||
from sqlalchemy import select
|
||||
|
||||
result = await db.execute(
|
||||
select(AgentSubtask)
|
||||
@@ -795,7 +800,7 @@ async def cancel_subtask(
|
||||
try:
|
||||
sid = uuid.UUID(subtask_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID") from None
|
||||
|
||||
from app.plugins.builtins.automation.agent_coordinator import AgentCoordinator
|
||||
|
||||
@@ -823,7 +828,7 @@ async def wait_for_subtask(
|
||||
try:
|
||||
sid = uuid.UUID(subtask_id)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID")
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID") from None
|
||||
|
||||
from app.plugins.builtins.automation.agent_coordinator import AgentCoordinator
|
||||
|
||||
@@ -845,7 +850,7 @@ async def aggregate_subtasks(
|
||||
try:
|
||||
ids = [uuid.UUID(sid) for sid in subtask_ids]
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID in list")
|
||||
raise HTTPException(status_code=400, detail="Invalid subtask ID in list") from None
|
||||
|
||||
from app.plugins.builtins.automation.agent_coordinator import AgentCoordinator
|
||||
|
||||
|
||||
@@ -74,6 +74,6 @@ async def scheduler_tick(ctx: dict[str, Any]) -> None:
|
||||
|
||||
|
||||
# Register all job functions with the job registry
|
||||
from app.core.job_registry import register_job
|
||||
from app.core.job_registry import register_job # noqa: E402
|
||||
|
||||
register_job("scheduler_tick", scheduler_tick)
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# ─── Agent Definition Schemas ───
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func, select, text, update
|
||||
from app.core.visibility import apply_visibility_filter
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.visibility import apply_visibility_filter
|
||||
from app.plugins.builtins.automation.models import (
|
||||
AgentDefinition,
|
||||
AgentRun,
|
||||
@@ -668,7 +668,7 @@ class CronJobService:
|
||||
now = datetime.now(UTC)
|
||||
result = await db.execute(
|
||||
select(AutomationCronJob)
|
||||
.where(AutomationCronJob.is_active == True)
|
||||
.where(AutomationCronJob.is_active.is_(True))
|
||||
.where(AutomationCronJob.next_run_at <= now)
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
@@ -7,33 +7,24 @@ since PostgreSQL may not be available in the dev container.
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from collections.abc import AsyncGenerator
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any, AsyncGenerator
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.core.db import Base
|
||||
from app.plugins.builtins.automation.models import (
|
||||
AgentDefinition,
|
||||
AgentRun,
|
||||
AgentVersion,
|
||||
AutomationCronJob,
|
||||
AutomationDefinition,
|
||||
AutomationRun,
|
||||
AutomationVersion,
|
||||
)
|
||||
from app.plugins.builtins.automation.services import (
|
||||
AgentService,
|
||||
AutomationService,
|
||||
CronJobService,
|
||||
RunLogService,
|
||||
)
|
||||
|
||||
|
||||
# ─── Fixtures ───
|
||||
|
||||
|
||||
@@ -536,12 +527,12 @@ class TestInfiniteLoopDetection:
|
||||
tool_call_count: dict[str, int] = {}
|
||||
tool_name = "send_email"
|
||||
|
||||
for i in range(5):
|
||||
for _i in range(5):
|
||||
tool_call_count[tool_name] = tool_call_count.get(tool_name, 0) + 1
|
||||
if tool_call_count[tool_name] >= 5:
|
||||
assert True
|
||||
return
|
||||
assert False, "Loop detection should have triggered"
|
||||
raise AssertionError("Loop detection should have triggered")
|
||||
|
||||
def test_different_tools_not_detected(self):
|
||||
"""Test that different tool calls don't trigger loop detection."""
|
||||
@@ -549,5 +540,5 @@ class TestInfiniteLoopDetection:
|
||||
for i in range(5):
|
||||
tool_call_count[f"tool_{i}"] = tool_call_count.get(f"tool_{i}", 0) + 1
|
||||
if tool_call_count[f"tool_{i}"] >= 5:
|
||||
assert False, "Different tools should not trigger loop detection"
|
||||
raise AssertionError("Different tools should not trigger loop detection")
|
||||
assert True
|
||||
|
||||
@@ -67,7 +67,7 @@ async def check_workflow_timeouts(ctx: dict[str, Any]) -> None:
|
||||
user_id=instance.initiated_by,
|
||||
type="workflow_timeout",
|
||||
title=f"Workflow '{workflow_name}' cancelled due to timeout",
|
||||
body=f"The workflow instance timed out and was automatically cancelled.",
|
||||
body="The workflow instance timed out and was automatically cancelled.",
|
||||
)
|
||||
db.add(notification)
|
||||
|
||||
@@ -83,6 +83,6 @@ async def check_workflow_timeouts(ctx: dict[str, Any]) -> None:
|
||||
|
||||
|
||||
# Register all job functions with the job registry
|
||||
from app.core.job_registry import register_job
|
||||
from app.core.job_registry import register_job # noqa: E402
|
||||
|
||||
register_job("check_workflow_timeouts", check_workflow_timeouts)
|
||||
|
||||
Reference in New Issue
Block a user