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:
Agent Zero
2026-08-16 01:17:18 +02:00
parent 3d9b76cea4
commit abbe7a18fc
306 changed files with 5912 additions and 1827 deletions
@@ -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.
+14 -13
View File
@@ -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)
+8 -10
View File
@@ -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)
+2 -1
View File
@@ -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()
+2 -1
View File
@@ -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
+34 -8
View File
@@ -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)
+31 -26
View File
@@ -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
+1 -1
View File
@@ -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 ───
+3 -3
View File
@@ -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)