mirror of
https://github.com/johndoe6345789/AutoMetabuilder.git
synced 2026-04-24 13:54:59 +00:00
Merge pull request #14 from johndoe6345789/copilot/convert-to-workflow-plugins
Convert integrations to workflow plugins with backend client initialization
This commit is contained in:
@@ -1,55 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.errors import SlackApiError
|
||||
import discord
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger("autometabuilder.notifications")
|
||||
|
||||
def send_slack_notification(message: str):
|
||||
token = os.environ.get("SLACK_BOT_TOKEN")
|
||||
channel = os.environ.get("SLACK_CHANNEL")
|
||||
if not token or not channel:
|
||||
logger.warning("Slack notification skipped: SLACK_BOT_TOKEN or SLACK_CHANNEL missing.")
|
||||
return
|
||||
|
||||
client = WebClient(token=token)
|
||||
try:
|
||||
client.chat_postMessage(channel=channel, text=message)
|
||||
logger.info("Slack notification sent successfully.")
|
||||
except SlackApiError as e:
|
||||
logger.error(f"Error sending Slack notification: {e}")
|
||||
|
||||
async def send_discord_notification_async(message: str):
|
||||
token = os.environ.get("DISCORD_BOT_TOKEN")
|
||||
channel_id = os.environ.get("DISCORD_CHANNEL_ID")
|
||||
if not token or not channel_id:
|
||||
logger.warning("Discord notification skipped: DISCORD_BOT_TOKEN or DISCORD_CHANNEL_ID missing.")
|
||||
return
|
||||
|
||||
intents = discord.Intents.default()
|
||||
client = discord.Client(intents=intents)
|
||||
|
||||
@client.event
|
||||
async def on_ready():
|
||||
channel = client.get_channel(int(channel_id))
|
||||
if channel:
|
||||
await channel.send(message)
|
||||
logger.info("Discord notification sent successfully.")
|
||||
await client.close()
|
||||
|
||||
try:
|
||||
await client.start(token)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending Discord notification: {e}")
|
||||
|
||||
def send_discord_notification(message: str):
|
||||
try:
|
||||
asyncio.run(send_discord_notification_async(message))
|
||||
except Exception as e:
|
||||
logger.error(f"Error running Discord notification: {e}")
|
||||
|
||||
def notify_all(message: str):
|
||||
send_slack_notification(message)
|
||||
send_discord_notification(message)
|
||||
73
backend/autometabuilder/workflow/notification_helpers.py
Normal file
73
backend/autometabuilder/workflow/notification_helpers.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Notification helpers for workflow plugins."""
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger("autometabuilder.notifications")
|
||||
|
||||
|
||||
def send_slack_notification(runtime, message: str):
|
||||
"""Send a notification to Slack using client from runtime context."""
|
||||
client = runtime.context.get("slack_client") if runtime else None
|
||||
channel = os.environ.get("SLACK_CHANNEL")
|
||||
|
||||
if not client:
|
||||
logger.warning("Slack notification skipped: Slack client not initialized.")
|
||||
return
|
||||
|
||||
if not channel:
|
||||
logger.warning("Slack notification skipped: SLACK_CHANNEL missing.")
|
||||
return
|
||||
|
||||
try:
|
||||
from slack_sdk.errors import SlackApiError
|
||||
client.chat_postMessage(channel=channel, text=message)
|
||||
logger.info("Slack notification sent successfully.")
|
||||
except SlackApiError as e:
|
||||
logger.error(f"Error sending Slack notification: {e}")
|
||||
|
||||
|
||||
async def send_discord_notification_async(message: str, token: str, intents, channel_id: str):
|
||||
"""Send Discord notification asynchronously."""
|
||||
import discord
|
||||
|
||||
client = discord.Client(intents=intents)
|
||||
|
||||
@client.event
|
||||
async def on_ready():
|
||||
channel = client.get_channel(int(channel_id))
|
||||
if channel:
|
||||
await channel.send(message)
|
||||
logger.info("Discord notification sent successfully.")
|
||||
await client.close()
|
||||
|
||||
try:
|
||||
await client.start(token)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending Discord notification: {e}")
|
||||
|
||||
|
||||
def send_discord_notification(runtime, message: str):
|
||||
"""Send a Discord notification using config from runtime context."""
|
||||
token = runtime.context.get("discord_token") if runtime else None
|
||||
intents = runtime.context.get("discord_intents") if runtime else None
|
||||
channel_id = os.environ.get("DISCORD_CHANNEL_ID")
|
||||
|
||||
if not token:
|
||||
logger.warning("Discord notification skipped: Discord client not initialized.")
|
||||
return
|
||||
|
||||
if not channel_id:
|
||||
logger.warning("Discord notification skipped: DISCORD_CHANNEL_ID missing.")
|
||||
return
|
||||
|
||||
try:
|
||||
asyncio.run(send_discord_notification_async(message, token, intents, channel_id))
|
||||
except Exception as e:
|
||||
logger.error(f"Error running Discord notification: {e}")
|
||||
|
||||
|
||||
def notify_all(runtime, message: str):
|
||||
"""Send notification to all configured channels."""
|
||||
send_slack_notification(runtime, message)
|
||||
send_discord_notification(runtime, message)
|
||||
@@ -5,9 +5,10 @@ This document describes all available workflow plugins for building declarative
|
||||
## Directory Structure
|
||||
|
||||
Plugins are now organized into subdirectories by category:
|
||||
- **backend/** - Backend infrastructure and initialization plugins (12 plugins)
|
||||
- **backend/** - Backend infrastructure and initialization plugins (14 plugins)
|
||||
- **core/** - Core workflow orchestration plugins (7 plugins)
|
||||
- **tools/** - Tool execution and development plugins (7 plugins)
|
||||
- **notifications/** - External notification integrations (3 plugins)
|
||||
- **logic/** - Logic and comparison operations (9 plugins)
|
||||
- **list/** - List/array operations (7 plugins)
|
||||
- **dict/** - Dictionary/object operations (6 plugins)
|
||||
@@ -18,13 +19,15 @@ Plugins are now organized into subdirectories by category:
|
||||
- **var/** - Variable management (4 plugins)
|
||||
- **test/** - Unit testing and assertions (5 plugins)
|
||||
- **utils/** - Utility functions (7 plugins)
|
||||
- **web/** - Web UI and Flask operations (26 plugins)
|
||||
|
||||
**Total: 90 plugins**
|
||||
**Total: 95 plugins**
|
||||
|
||||
## Categories
|
||||
|
||||
- [Core Plugins](#core-plugins) - AI and context management
|
||||
- [Tool Plugins](#tool-plugins) - File system and SDLC operations
|
||||
- [Notification Plugins](#notification-plugins) - External notification integrations
|
||||
- [Logic Plugins](#logic-plugins) - Boolean logic and comparisons
|
||||
- [List Plugins](#list-plugins) - Collection operations
|
||||
- [Dictionary Plugins](#dictionary-plugins) - Object/map operations
|
||||
@@ -36,6 +39,7 @@ Plugins are now organized into subdirectories by category:
|
||||
- [Test Plugins](#test-plugins) - Unit testing and assertions
|
||||
- [Backend Plugins](#backend-plugins) - System initialization
|
||||
- [Utility Plugins](#utility-plugins) - General utilities
|
||||
- [Web Plugins](#web-plugins) - Web UI and Flask operations
|
||||
|
||||
---
|
||||
|
||||
@@ -172,6 +176,57 @@ Run command inside Docker container.
|
||||
|
||||
---
|
||||
|
||||
## Notification Plugins
|
||||
|
||||
**Note:** Notification plugins require the corresponding backend plugins (`backend.create_slack` and/or `backend.create_discord`) to be run first to initialize the clients.
|
||||
|
||||
### `notifications.slack`
|
||||
Send notification to Slack.
|
||||
|
||||
**Prerequisites:**
|
||||
- `backend.create_slack` must be run first to initialize the Slack client
|
||||
|
||||
**Inputs:**
|
||||
- `message` - The message to send
|
||||
- `channel` - Optional channel (defaults to SLACK_CHANNEL env var)
|
||||
|
||||
**Outputs:**
|
||||
- `success` - Boolean (true if sent successfully)
|
||||
- `message` - Status message
|
||||
- `error` - Error message (if failed)
|
||||
- `skipped` - Boolean (true if skipped due to missing config)
|
||||
|
||||
### `notifications.discord`
|
||||
Send notification to Discord.
|
||||
|
||||
**Prerequisites:**
|
||||
- `backend.create_discord` must be run first to initialize the Discord configuration
|
||||
|
||||
**Inputs:**
|
||||
- `message` - The message to send
|
||||
- `channel_id` - Optional channel ID (defaults to DISCORD_CHANNEL_ID env var)
|
||||
|
||||
**Outputs:**
|
||||
- `success` - Boolean (true if sent successfully)
|
||||
- `message` - Status message
|
||||
- `error` - Error message (if failed)
|
||||
- `skipped` - Boolean (true if skipped due to missing config)
|
||||
|
||||
### `notifications.all`
|
||||
Send notification to all configured channels (Slack and Discord).
|
||||
|
||||
**Prerequisites:**
|
||||
- `backend.create_slack` and `backend.create_discord` should be run first for full functionality
|
||||
|
||||
**Inputs:**
|
||||
- `message` - The message to send to all channels
|
||||
|
||||
**Outputs:**
|
||||
- `success` - Boolean
|
||||
- `message` - Status message
|
||||
|
||||
---
|
||||
|
||||
## Logic Plugins
|
||||
|
||||
### `logic.and`
|
||||
@@ -812,6 +867,28 @@ Initialize OpenAI client.
|
||||
- `result` - OpenAI client
|
||||
- `initialized` - Boolean
|
||||
|
||||
### `backend.create_slack`
|
||||
Initialize Slack WebClient.
|
||||
|
||||
**Inputs:**
|
||||
- `token` - Optional Slack bot token (defaults to SLACK_BOT_TOKEN env var)
|
||||
|
||||
**Outputs:**
|
||||
- `result` - Slack client
|
||||
- `initialized` - Boolean
|
||||
- `error` - Error message (if failed)
|
||||
|
||||
### `backend.create_discord`
|
||||
Initialize Discord client configuration.
|
||||
|
||||
**Inputs:**
|
||||
- `token` - Optional Discord bot token (defaults to DISCORD_BOT_TOKEN env var)
|
||||
|
||||
**Outputs:**
|
||||
- `result` - Discord token
|
||||
- `initialized` - Boolean
|
||||
- `error` - Error message (if failed)
|
||||
|
||||
### `backend.load_metadata`
|
||||
Load metadata.json.
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Workflow plugin: create Discord client."""
|
||||
import os
|
||||
import logging
|
||||
import discord
|
||||
|
||||
logger = logging.getLogger("autometabuilder")
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""
|
||||
Initialize Discord Client (without starting the connection).
|
||||
|
||||
Note: Discord client needs to be started asynchronously. This plugin
|
||||
just stores the token and intents configuration for later use by
|
||||
notification plugins.
|
||||
|
||||
Inputs:
|
||||
token: Optional Discord bot token (defaults to DISCORD_BOT_TOKEN env var)
|
||||
|
||||
Returns:
|
||||
dict: Contains initialization status and configuration
|
||||
"""
|
||||
token = inputs.get("token") or os.environ.get("DISCORD_BOT_TOKEN")
|
||||
|
||||
if not token:
|
||||
logger.warning("Discord client not initialized: DISCORD_BOT_TOKEN missing.")
|
||||
runtime.context["discord_token"] = None
|
||||
return {"result": None, "initialized": False, "error": "DISCORD_BOT_TOKEN missing"}
|
||||
|
||||
# Store token and intents configuration in context
|
||||
# Discord client must be created per-use due to its async nature
|
||||
runtime.context["discord_token"] = token
|
||||
runtime.context["discord_intents"] = discord.Intents.default()
|
||||
|
||||
logger.info("Discord configuration initialized successfully.")
|
||||
return {"result": token, "initialized": True}
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Workflow plugin: create Slack client."""
|
||||
import os
|
||||
import logging
|
||||
from slack_sdk import WebClient
|
||||
|
||||
logger = logging.getLogger("autometabuilder")
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""
|
||||
Initialize Slack WebClient.
|
||||
|
||||
Inputs:
|
||||
token: Optional Slack bot token (defaults to SLACK_BOT_TOKEN env var)
|
||||
|
||||
Returns:
|
||||
dict: Contains the Slack client in result and initialized status
|
||||
"""
|
||||
token = inputs.get("token") or os.environ.get("SLACK_BOT_TOKEN")
|
||||
|
||||
if not token:
|
||||
logger.warning("Slack client not initialized: SLACK_BOT_TOKEN missing.")
|
||||
runtime.context["slack_client"] = None
|
||||
return {"result": None, "initialized": False, "error": "SLACK_BOT_TOKEN missing"}
|
||||
|
||||
# Create Slack client
|
||||
client = WebClient(token=token)
|
||||
|
||||
# Store in context for other plugins to use
|
||||
runtime.context["slack_client"] = client
|
||||
|
||||
logger.info("Slack client initialized successfully.")
|
||||
return {"result": client, "initialized": True}
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Workflow plugin: append tool results."""
|
||||
import os
|
||||
import re
|
||||
from ....integrations.notifications import notify_all
|
||||
from ...notification_helpers import notify_all
|
||||
|
||||
|
||||
def _is_mvp_reached() -> bool:
|
||||
@@ -47,6 +47,6 @@ def run(runtime, inputs):
|
||||
|
||||
if runtime.context["args"].yolo and _is_mvp_reached():
|
||||
runtime.logger.info("MVP reached. Stopping YOLO loop.")
|
||||
notify_all("AutoMetabuilder YOLO loop stopped: MVP reached.")
|
||||
notify_all(runtime, "AutoMetabuilder YOLO loop stopped: MVP reached.")
|
||||
|
||||
return {"messages": messages}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Workflow plugin: run tool calls."""
|
||||
from ....integrations.notifications import notify_all
|
||||
from ...notification_helpers import notify_all
|
||||
from ..tool_calls_handler import handle_tool_calls
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ def run(runtime, inputs):
|
||||
runtime.logger
|
||||
)
|
||||
if not tool_calls and resp_msg.content:
|
||||
notify_all(f"AutoMetabuilder task complete: {resp_msg.content[:100]}...")
|
||||
notify_all(runtime, f"AutoMetabuilder task complete: {resp_msg.content[:100]}...")
|
||||
return {
|
||||
"tool_results": tool_results,
|
||||
"no_tool_calls": not bool(tool_calls)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Workflow plugin: send notification to all channels."""
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger("autometabuilder.notifications")
|
||||
|
||||
|
||||
def _send_slack(client, message: str, channel: str):
|
||||
"""Send Slack notification using provided client."""
|
||||
if not client or not channel:
|
||||
logger.warning("Slack notification skipped: client or channel missing.")
|
||||
return
|
||||
|
||||
try:
|
||||
from slack_sdk.errors import SlackApiError
|
||||
client.chat_postMessage(channel=channel, text=message)
|
||||
logger.info("Slack notification sent successfully.")
|
||||
except SlackApiError as e:
|
||||
logger.error(f"Error sending Slack notification: {e}")
|
||||
|
||||
|
||||
async def _send_discord_async(message: str, token: str, intents, channel_id: str):
|
||||
"""Send Discord notification asynchronously."""
|
||||
import discord
|
||||
|
||||
client = discord.Client(intents=intents)
|
||||
|
||||
@client.event
|
||||
async def on_ready():
|
||||
channel = client.get_channel(int(channel_id))
|
||||
if channel:
|
||||
await channel.send(message)
|
||||
logger.info("Discord notification sent successfully.")
|
||||
await client.close()
|
||||
|
||||
try:
|
||||
await client.start(token)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending Discord notification: {e}")
|
||||
|
||||
|
||||
def _send_discord(message: str, token: str, intents, channel_id: str):
|
||||
"""Send Discord notification."""
|
||||
if not token or not channel_id:
|
||||
logger.warning("Discord notification skipped: token or channel_id missing.")
|
||||
return
|
||||
|
||||
try:
|
||||
asyncio.run(_send_discord_async(message, token, intents, channel_id))
|
||||
except Exception as e:
|
||||
logger.error(f"Error running Discord notification: {e}")
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""
|
||||
Send a notification to all configured channels (Slack and Discord).
|
||||
|
||||
Inputs:
|
||||
message: The message to send to all channels
|
||||
|
||||
Returns:
|
||||
dict: Contains success status for all channels
|
||||
"""
|
||||
message = inputs.get("message", "")
|
||||
|
||||
# Get Slack client from runtime context
|
||||
slack_client = runtime.context.get("slack_client")
|
||||
slack_channel = os.environ.get("SLACK_CHANNEL")
|
||||
|
||||
# Get Discord config from runtime context
|
||||
discord_token = runtime.context.get("discord_token")
|
||||
discord_intents = runtime.context.get("discord_intents")
|
||||
discord_channel_id = os.environ.get("DISCORD_CHANNEL_ID")
|
||||
|
||||
# Send to both channels
|
||||
_send_slack(slack_client, message, slack_channel)
|
||||
_send_discord(message, discord_token, discord_intents, discord_channel_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Notifications sent to all channels"
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Workflow plugin: send Discord notification."""
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger("autometabuilder.notifications")
|
||||
|
||||
|
||||
async def _send_discord_notification_async(message: str, token: str, intents, channel_id: str):
|
||||
"""Send Discord notification asynchronously."""
|
||||
# Import discord here to avoid loading it at module level
|
||||
import discord
|
||||
|
||||
client = discord.Client(intents=intents)
|
||||
|
||||
@client.event
|
||||
async def on_ready():
|
||||
channel = client.get_channel(int(channel_id))
|
||||
if channel:
|
||||
await channel.send(message)
|
||||
logger.info("Discord notification sent successfully.")
|
||||
await client.close()
|
||||
|
||||
try:
|
||||
await client.start(token)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending Discord notification: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""
|
||||
Send a notification to Discord.
|
||||
|
||||
Inputs:
|
||||
message: The message to send
|
||||
channel_id: Optional channel ID (defaults to DISCORD_CHANNEL_ID env var)
|
||||
|
||||
Returns:
|
||||
dict: Contains success status and any error message
|
||||
"""
|
||||
message = inputs.get("message", "")
|
||||
channel_id = inputs.get("channel_id") or os.environ.get("DISCORD_CHANNEL_ID")
|
||||
|
||||
# Get Discord token and intents from runtime context (initialized by backend.create_discord)
|
||||
token = runtime.context.get("discord_token")
|
||||
intents = runtime.context.get("discord_intents")
|
||||
|
||||
if not token:
|
||||
logger.warning("Discord notification skipped: Discord client not initialized.")
|
||||
return {
|
||||
"success": False,
|
||||
"skipped": True,
|
||||
"error": "Discord client not initialized"
|
||||
}
|
||||
|
||||
if not channel_id:
|
||||
logger.warning("Discord notification skipped: DISCORD_CHANNEL_ID missing.")
|
||||
return {
|
||||
"success": False,
|
||||
"skipped": True,
|
||||
"error": "DISCORD_CHANNEL_ID missing"
|
||||
}
|
||||
|
||||
try:
|
||||
asyncio.run(_send_discord_notification_async(message, token, intents, channel_id))
|
||||
return {"success": True, "message": "Discord notification sent"}
|
||||
except Exception as e:
|
||||
logger.error(f"Error running Discord notification: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Workflow plugin: send Slack notification."""
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("autometabuilder.notifications")
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""
|
||||
Send a notification to Slack.
|
||||
|
||||
Inputs:
|
||||
message: The message to send
|
||||
channel: Optional channel (defaults to SLACK_CHANNEL env var)
|
||||
|
||||
Returns:
|
||||
dict: Contains success status and any error message
|
||||
"""
|
||||
message = inputs.get("message", "")
|
||||
channel = inputs.get("channel") or os.environ.get("SLACK_CHANNEL")
|
||||
|
||||
# Get Slack client from runtime context (initialized by backend.create_slack)
|
||||
client = runtime.context.get("slack_client")
|
||||
|
||||
if not client:
|
||||
logger.warning("Slack notification skipped: Slack client not initialized.")
|
||||
return {
|
||||
"success": False,
|
||||
"skipped": True,
|
||||
"error": "Slack client not initialized"
|
||||
}
|
||||
|
||||
if not channel:
|
||||
logger.warning("Slack notification skipped: SLACK_CHANNEL missing.")
|
||||
return {
|
||||
"success": False,
|
||||
"skipped": True,
|
||||
"error": "SLACK_CHANNEL missing"
|
||||
}
|
||||
|
||||
try:
|
||||
# Import SlackApiError here to handle errors from the client
|
||||
from slack_sdk.errors import SlackApiError
|
||||
client.chat_postMessage(channel=channel, text=message)
|
||||
logger.info("Slack notification sent successfully.")
|
||||
return {"success": True, "message": "Slack notification sent"}
|
||||
except SlackApiError as e:
|
||||
logger.error(f"Error sending Slack notification: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
Reference in New Issue
Block a user