fix: connect 10 unconnected backend modules to real code paths (context_builder→agent_runner, agent_permissions→agent_runner, agent_tools→agent_runner, data_policy→agent_runner, oversight→agent_runner+migration 0128, transparency→agent_runner, agent_stream→agent_routes SSE endpoint, agent_memory AI-module deleted, decision_guard→engine, require_approval→agent_runner), 11 integration tests passing
Check Cross-Plugin Imports / check (push) Has been cancelled

This commit is contained in:
Agent Zero
2026-08-19 16:25:20 +02:00
parent f3fbb5d1e8
commit 8ee88d9b41
8 changed files with 978 additions and 237 deletions
@@ -601,3 +601,55 @@ async def send_agent_message_endpoint(
)
return result
# ─── Punkt 7: SSE Streaming Endpoint (agent_stream.py) ─────────────────────
@router.post("/{id}/stream")
async def stream_agent_run(
id: str,
body: AgentMessageRequest,
current_user: dict[str, Any] = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Stream an agent run via Server-Sent Events (SSE).
Uses ``app.ai.agent_stream.stream_react_loop`` to emit step events
in real-time as the agent processes.
"""
from fastapi.responses import StreamingResponse
from app.ai.agent_stream import stream_react_loop
from app.plugins.builtins.ai_assistant.contracts import get_tool_registry
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
try:
aid = uuid.UUID(id)
except (ValueError, TypeError):
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:
raise HTTPException(status_code=404, detail="Agent not found")
if not agent.is_active:
raise HTTPException(status_code=400, detail="Agent is not active")
registry = get_tool_registry()
tool_ids: list[str] = list(agent.tool_ids or [])
tools = registry.get_by_names(tool_ids) if tool_ids else []
tool_schemas = [t.to_openai_schema() for t in tools] if tools else []
return StreamingResponse(
stream_react_loop(
agent_definition=agent,
user_message=body.message,
tools=tool_schemas,
tool_registry=registry,
db=db,
tenant_id=tenant_id,
user_id=user_id,
agent_run_id=aid,
),
media_type="text/event-stream",
)
@@ -155,6 +155,28 @@ async def run_agent(
tools = registry.get_by_names(tool_ids) if tool_ids else []
tool_schemas = [t.to_openai_schema() for t in tools] if tools else []
# ── Resolve agent permissions (Punkt 2+3 der Audit) ──
from app.ai.agent_permissions import resolve_agent_permissions
from app.ai.agent_tools import get_agent_tools
from app.ai.skill_registry import get_skill_registry
async with factory() as db:
perm_ctx = await resolve_agent_permissions(
db=db,
tenant_id=agent.tenant_id,
user_id=agent.created_by or uuid_mod.uuid4(),
agent_definition=agent,
)
# Use permission-filtered tools instead of raw tool_ids
skill_reg = get_skill_registry()
tool_schemas, _skills = get_agent_tools(
agent_definition=agent,
tool_registry=registry,
skill_registry=skill_reg,
user_permissions=perm_ctx.user_permissions,
)
# ── Create AgentRun record ──
run_id: uuid.UUID | None = None
started_at = datetime.now(UTC)
@@ -192,10 +214,38 @@ async def run_agent(
import asyncio
import uuid as uuid_mod
# ── Build agent context via context_builder (Punkt 1 der Audit) ──
from app.ai.context_builder import build_agent_context
# Sanitize context_data to remove sensitive fields (Punkt 4: data_policy)
from app.core.sensitive_data import sanitize_dict
safe_context_data = sanitize_dict(context_data)
# Build the user message from sanitized context
user_message = f"Context: {safe_context_data}" if safe_context_data else "No additional context provided."
# Build full message list (system prompt + context + user message)
messages = await build_agent_context(
agent_definition=agent,
user_message=user_message,
db=None, # No DB session available here; context_builder handles gracefully
tenant_id=agent.tenant_id,
user_id=agent.created_by or uuid_mod.uuid4(),
)
# ── Enforce data policy: filter sensitive fields from messages (Punkt 4) ──
from app.ai.data_policy import enforce_data_policy
messages = await enforce_data_policy(
db=None,
tenant_id=agent.tenant_id,
messages=messages,
agent_definition=agent,
)
react_result: ReActResult = await asyncio.wait_for(
run_react_loop(
agent_definition=agent,
messages=[{"role": "user", "content": f"Context: {context_data}"}],
messages=messages,
tools=tool_schemas,
tool_registry=registry,
db=None, # ReAct loop doesn't need DB session for LLM calls directly
@@ -204,6 +254,8 @@ async def run_agent(
agent_run_id=run_id,
max_steps=20,
timeout_seconds=max_duration,
require_approval=bool(getattr(agent, "require_approval", False)),
approval_tools=getattr(agent, "approval_tools", None),
),
timeout=max_duration + 10, # Extra buffer beyond loop's own timeout
)
@@ -212,6 +264,40 @@ async def run_agent(
result_data["llm_response"] = react_result.final_content
result_data["cost_usd"] = react_result.total_cost_usd
result_data["error"] = react_result.error
# ── Mark result as AI-generated (Punkt 6: transparency) ──
from app.ai.transparency import mark_as_ai_generated
if react_result.final_content:
ai_metadata = mark_as_ai_generated(
react_result.final_content,
metadata={
"model": getattr(agent, "llm_model", "unknown"),
"provider": getattr(agent, "provider", "unknown"),
"agent_id": str(agent.id),
"agent_name": agent.name,
"run_id": str(run_id) if run_id else None,
},
)
result_data["ai_generated"] = True
result_data["ai_metadata"] = ai_metadata.get("ai_metadata", {})
# ── Create oversight decision record (Punkt 5: oversight) ──
from app.ai.oversight import DecisionRecord, create_decision_record
try:
async with factory() as db:
record = DecisionRecord(
agent_run_id=run_id or uuid_mod.uuid4(),
recommendation=react_result.final_content,
evidence={
"steps": len(react_result.steps),
"cost_usd": react_result.total_cost_usd,
"status": react_result.status,
},
)
await create_decision_record(db, agent.tenant_id, record)
await db.commit()
except Exception as e:
logger.warning("Failed to create oversight decision record: %s", e)
result_data["steps"] = [
{
"step_number": s.step_number,