335762dd3d
- Windows-Host: openMiniAppWindow-Helper + MiniAppWindowContent (windowStore); Oeffnen-Buttons im Chat-Block (MiniAppBlock) und Dashboard-Widget - AI-Agenten-Host: Core-Tool send_miniapp (app/ai/miniapp_tools.py) — miniapp-Block in Agent-Chat (approval_request-Praezedenz), Permission fail-closed gegen aufrufenden User pro App; Registrierung im lifespan - agent_loop: tool_context + agent_name (Raum-Aufloesung) - Fix: MiniAppBlock nutzt useMiniapps (component-Feld) statt Legacy /comm/miniapps - Tests: M6 7/7 (TDD rot->gruen), Backend-Regression 57/57, Vitest 26/26 (4 neue Window-Tests), tsc clean, build OK
128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
"""Core AI agent tools for MiniApp output (Phase M6).
|
|
|
|
``send_miniapp`` lets an agent embed a MiniApp as an interactive output
|
|
block in its chat room (block_type "miniapp", approval_request precedent
|
|
from agent_loop). Permission is checked fail-closed against the CALLING
|
|
user for the target app's own permission — the tool never widens access.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from app.ai.tool_registry import get_tool_registry
|
|
from app.core.permissions import check_permission, resolve_permissions
|
|
from app.plugins.miniapp_registry import get_miniapp_registry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_komm_contract() -> Any | None:
|
|
"""Resolve the kommunikation contract (None when plugin inactive)."""
|
|
from app.plugins.builtins.contracts import get_contract_registry
|
|
|
|
return get_contract_registry().get("kommunikation")
|
|
|
|
|
|
async def _send_miniapp_handler(arguments: dict[str, Any], context: dict[str, Any]) -> str:
|
|
"""Send a MiniApp as an output block to the agent's chat room."""
|
|
app_id = str(arguments.get("app_id") or "")
|
|
settings = arguments.get("settings") or {}
|
|
if not isinstance(settings, dict):
|
|
settings = {}
|
|
|
|
app = get_miniapp_registry().get_app(app_id)
|
|
if app is None:
|
|
return f"Error: MiniApp '{app_id}' not found"
|
|
|
|
db = context.get("db")
|
|
tenant_id = context.get("tenant_id")
|
|
user_id = context.get("user_id")
|
|
if not tenant_id or not user_id or db is None:
|
|
return "Error: Missing tenant/user context"
|
|
|
|
try:
|
|
tenant_uuid = uuid.UUID(str(tenant_id))
|
|
user_uuid = uuid.UUID(str(user_id))
|
|
except (ValueError, TypeError):
|
|
return "Error: Invalid tenant/user context"
|
|
|
|
# Fail-closed permission check against the calling user
|
|
resolved = await resolve_permissions(db, user_uuid, tenant_uuid)
|
|
if app.permission and not check_permission(resolved, app.permission):
|
|
return f"Error: Permission '{app.permission}' required for MiniApp '{app.app_id}'"
|
|
|
|
komm = _get_komm_contract()
|
|
if komm is None:
|
|
return "Error: Communication plugin not available"
|
|
|
|
agent_name = str(context.get("agent_name") or "Agent")
|
|
conv_id = await komm.find_locked_room_id(
|
|
db=db,
|
|
tenant_id=tenant_uuid,
|
|
plugin_name="automation",
|
|
title=f"Agent: {agent_name}",
|
|
)
|
|
if conv_id is None:
|
|
return f"Info: No agent chat room found for '{agent_name}' — MiniApp not posted"
|
|
|
|
agent_id_raw = context.get("agent_id")
|
|
try:
|
|
sender_id = uuid.UUID(str(agent_id_raw)) if agent_id_raw else None
|
|
except (ValueError, TypeError):
|
|
sender_id = None
|
|
|
|
await komm.send_message(
|
|
db=db,
|
|
tenant_id=tenant_uuid,
|
|
conversation_id=conv_id,
|
|
sender_id=sender_id,
|
|
sender_type="agent",
|
|
content=f"MiniApp: {app.name}",
|
|
content_format="text",
|
|
blocks=[
|
|
{
|
|
"block_type": "miniapp",
|
|
"block_data": {"app_id": app_id, "config": settings},
|
|
"sort_order": 0,
|
|
}
|
|
],
|
|
)
|
|
return f"MiniApp '{app_id}' sent to chat"
|
|
|
|
|
|
# ─── Registration ───
|
|
|
|
|
|
def register_miniapp_tools() -> None:
|
|
"""Register the MiniApp agent tools in the global tool registry."""
|
|
registry = get_tool_registry()
|
|
registry.register(
|
|
name="send_miniapp",
|
|
description=(
|
|
"Eine MiniApp als interaktiven Ausgabe-Block in den Agent-Chat senden "
|
|
"(z.B. ein Widget mit Einstellungen anzeigen). Verfügbare App-IDs "
|
|
"stehen in /api/v1/miniapps."
|
|
),
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"app_id": {
|
|
"type": "string",
|
|
"description": "ID der MiniApp (z.B. recent_contacts, tasks_summary)",
|
|
},
|
|
"settings": {
|
|
"type": "object",
|
|
"description": "Optionale Einstellungen für die MiniApp-Instanz",
|
|
},
|
|
},
|
|
"required": ["app_id"],
|
|
},
|
|
handler=_send_miniapp_handler,
|
|
plugin_name="system",
|
|
required_permission=None, # per-app check inside the handler (fail-closed)
|
|
category="ui",
|
|
)
|