Make dependencies plugins - refactor notifications to use backend clients

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-10 17:02:50 +00:00
parent ef16336084
commit 9ad907ac03
11 changed files with 253 additions and 67 deletions

View File

@@ -0,0 +1,55 @@
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)

View File

@@ -1,39 +1,36 @@
"""Notification helpers for workflow plugins."""
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):
"""Send a notification to Slack."""
token = os.environ.get("SLACK_BOT_TOKEN")
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 token or not channel:
logger.warning("Slack notification skipped: SLACK_BOT_TOKEN or SLACK_CHANNEL missing.")
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
client = WebClient(token=token)
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):
async def send_discord_notification_async(message: str, token: str, intents, channel_id: str):
"""Send Discord notification asynchronously."""
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()
import discord
client = discord.Client(intents=intents)
@client.event
@@ -50,15 +47,27 @@ async def send_discord_notification_async(message: str):
logger.error(f"Error sending Discord notification: {e}")
def send_discord_notification(message: str):
"""Send a Discord notification."""
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))
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(message: str):
def notify_all(runtime, message: str):
"""Send notification to all configured channels."""
send_slack_notification(message)
send_discord_notification(message)
send_slack_notification(runtime, message)
send_discord_notification(runtime, message)

View File

@@ -5,7 +5,7 @@ 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)
@@ -21,7 +21,7 @@ Plugins are now organized into subdirectories by category:
- **utils/** - Utility functions (7 plugins)
- **web/** - Web UI and Flask operations (26 plugins)
**Total: 93 plugins**
**Total: 95 plugins**
## Categories
@@ -178,12 +178,16 @@ 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
- `token` - Optional Slack bot token (defaults to SLACK_BOT_TOKEN env var)
- `channel` - Optional channel (defaults to SLACK_CHANNEL env var)
**Outputs:**
@@ -195,9 +199,11 @@ Send notification to Slack.
### `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
- `token` - Optional Discord bot token (defaults to DISCORD_BOT_TOKEN env var)
- `channel_id` - Optional channel ID (defaults to DISCORD_CHANNEL_ID env var)
**Outputs:**
@@ -209,6 +215,9 @@ Send notification to Discord.
### `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
@@ -858,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.

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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)

View File

@@ -1,39 +1,29 @@
"""Workflow plugin: send notification to all channels."""
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(message: str):
"""Send Slack notification."""
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.")
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
client = WebClient(token=token)
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):
async def _send_discord_async(message: str, token: str, intents, channel_id: str):
"""Send Discord notification asynchronously."""
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
import discord
intents = discord.Intents.default()
client = discord.Client(intents=intents)
@client.event
@@ -50,10 +40,14 @@ async def _send_discord_async(message: str):
logger.error(f"Error sending Discord notification: {e}")
def _send_discord(message: str):
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))
asyncio.run(_send_discord_async(message, token, intents, channel_id))
except Exception as e:
logger.error(f"Error running Discord notification: {e}")
@@ -70,9 +64,18 @@ def run(runtime, inputs):
"""
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(message)
_send_discord(message)
_send_slack(slack_client, message, slack_channel)
_send_discord(message, discord_token, discord_intents, discord_channel_id)
return {
"success": True,

View File

@@ -1,15 +1,16 @@
"""Workflow plugin: send Discord notification."""
import os
import logging
import discord
import asyncio
logger = logging.getLogger("autometabuilder.notifications")
async def _send_discord_notification_async(message: str, token: str, channel_id: str):
async def _send_discord_notification_async(message: str, token: str, intents, channel_id: str):
"""Send Discord notification asynchronously."""
intents = discord.Intents.default()
# Import discord here to avoid loading it at module level
import discord
client = discord.Client(intents=intents)
@client.event
@@ -33,26 +34,36 @@ def run(runtime, inputs):
Inputs:
message: The message to send
token: Optional Discord bot token (defaults to DISCORD_BOT_TOKEN env var)
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", "")
token = inputs.get("token") or os.environ.get("DISCORD_BOT_TOKEN")
channel_id = inputs.get("channel_id") or 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.")
# 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_BOT_TOKEN or DISCORD_CHANNEL_ID missing"
"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, channel_id))
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}")

View File

@@ -1,8 +1,6 @@
"""Workflow plugin: send Slack notification."""
import os
import logging
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
logger = logging.getLogger("autometabuilder.notifications")
@@ -13,26 +11,36 @@ def run(runtime, inputs):
Inputs:
message: The message to send
token: Optional Slack bot token (defaults to SLACK_BOT_TOKEN env var)
channel: Optional channel (defaults to SLACK_CHANNEL env var)
Returns:
dict: Contains success status and any error message
"""
message = inputs.get("message", "")
token = inputs.get("token") or os.environ.get("SLACK_BOT_TOKEN")
channel = inputs.get("channel") or os.environ.get("SLACK_CHANNEL")
if not token or not channel:
logger.warning("Slack notification skipped: SLACK_BOT_TOKEN or SLACK_CHANNEL missing.")
# 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_BOT_TOKEN or SLACK_CHANNEL missing"
"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"
}
client = WebClient(token=token)
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"}