open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743364176__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- open_swarm-0.1.1743364176.dist-info/METADATA +286 -0
- open_swarm-0.1.1743364176.dist-info/RECORD +260 -0
- {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/WHEEL +1 -2
- open_swarm-0.1.1743364176.dist-info/entry_points.txt +2 -0
- swarm/__init__.py +0 -2
- swarm/auth.py +53 -49
- swarm/blueprints/README.md +67 -0
- swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
- swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
- swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
- swarm/blueprints/divine_code/__init__.py +0 -0
- swarm/blueprints/divine_code/apps.py +11 -0
- swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
- swarm/blueprints/django_chat/apps.py +6 -0
- swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
- swarm/blueprints/django_chat/urls.py +8 -0
- swarm/blueprints/django_chat/views.py +32 -0
- swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
- swarm/blueprints/family_ties/apps.py +11 -0
- swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
- swarm/blueprints/family_ties/models.py +19 -0
- swarm/blueprints/family_ties/serializers.py +7 -0
- swarm/blueprints/family_ties/settings.py +16 -0
- swarm/blueprints/family_ties/urls.py +10 -0
- swarm/blueprints/family_ties/views.py +26 -0
- swarm/blueprints/flock/__init__.py +0 -0
- swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
- swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
- swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
- swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
- swarm/blueprints/rue_code/__init__.py +0 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
- swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
- swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
- swarm/extensions/blueprint/__init__.py +30 -15
- swarm/extensions/blueprint/agent_utils.py +16 -40
- swarm/extensions/blueprint/blueprint_base.py +141 -543
- swarm/extensions/blueprint/blueprint_discovery.py +112 -98
- swarm/extensions/blueprint/cli_handler.py +185 -0
- swarm/extensions/blueprint/config_loader.py +122 -0
- swarm/extensions/blueprint/django_utils.py +181 -79
- swarm/extensions/blueprint/interactive_mode.py +1 -1
- swarm/extensions/config/config_loader.py +83 -200
- swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_cli.py +199 -287
- swarm/llm/chat_completion.py +26 -55
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +58 -0
- swarm/permissions.py +38 -0
- swarm/serializers.py +96 -5
- swarm/settings.py +95 -110
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
- swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
- swarm/static/contrib/markedjs/marked.min.js +6 -0
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
- swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
- swarm/static/contrib/tabler-icons/archive.svg +21 -0
- swarm/static/contrib/tabler-icons/artboard.svg +27 -0
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
- swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
- swarm/static/contrib/tabler-icons/carambola.svg +19 -0
- swarm/static/contrib/tabler-icons/copy.svg +20 -0
- swarm/static/contrib/tabler-icons/download.svg +21 -0
- swarm/static/contrib/tabler-icons/edit.svg +21 -0
- swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
- swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
- swarm/static/contrib/tabler-icons/headset.svg +22 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
- swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
- swarm/static/contrib/tabler-icons/message-star.svg +22 -0
- swarm/static/contrib/tabler-icons/message-x.svg +23 -0
- swarm/static/contrib/tabler-icons/message.svg +21 -0
- swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
- swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
- swarm/static/contrib/tabler-icons/robot.svg +26 -0
- swarm/static/contrib/tabler-icons/search.svg +19 -0
- swarm/static/contrib/tabler-icons/settings.svg +20 -0
- swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
- swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
- swarm/static/css/dropdown.css +22 -0
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +23 -0
- swarm/static/rest_mode/css/base.css +470 -0
- swarm/static/rest_mode/css/chat-history.css +286 -0
- swarm/static/rest_mode/css/chat.css +251 -0
- swarm/static/rest_mode/css/chatbot.css +74 -0
- swarm/static/rest_mode/css/chatgpt.css +62 -0
- swarm/static/rest_mode/css/colors/corporate.css +74 -0
- swarm/static/rest_mode/css/colors/pastel.css +81 -0
- swarm/static/rest_mode/css/colors/tropical.css +82 -0
- swarm/static/rest_mode/css/general.css +142 -0
- swarm/static/rest_mode/css/layout.css +167 -0
- swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
- swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
- swarm/static/rest_mode/css/messages.css +84 -0
- swarm/static/rest_mode/css/messenger.css +135 -0
- swarm/static/rest_mode/css/settings.css +91 -0
- swarm/static/rest_mode/css/simple.css +44 -0
- swarm/static/rest_mode/css/slack.css +58 -0
- swarm/static/rest_mode/css/style.css +156 -0
- swarm/static/rest_mode/css/theme.css +30 -0
- swarm/static/rest_mode/css/toast.css +40 -0
- swarm/static/rest_mode/js/auth.js +9 -0
- swarm/static/rest_mode/js/blueprint.js +41 -0
- swarm/static/rest_mode/js/blueprintUtils.js +12 -0
- swarm/static/rest_mode/js/chatLogic.js +79 -0
- swarm/static/rest_mode/js/debug.js +63 -0
- swarm/static/rest_mode/js/events.js +98 -0
- swarm/static/rest_mode/js/main.js +19 -0
- swarm/static/rest_mode/js/messages.js +264 -0
- swarm/static/rest_mode/js/messengerLogic.js +355 -0
- swarm/static/rest_mode/js/modules/apiService.js +84 -0
- swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
- swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
- swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
- swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
- swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
- swarm/static/rest_mode/js/modules/state.js +7 -0
- swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
- swarm/static/rest_mode/js/modules/validation.js +23 -0
- swarm/static/rest_mode/js/rendering.js +119 -0
- swarm/static/rest_mode/js/settings.js +130 -0
- swarm/static/rest_mode/js/sidebar.js +94 -0
- swarm/static/rest_mode/js/simpleLogic.js +37 -0
- swarm/static/rest_mode/js/slackLogic.js +66 -0
- swarm/static/rest_mode/js/splash.js +76 -0
- swarm/static/rest_mode/js/theme.js +111 -0
- swarm/static/rest_mode/js/toast.js +36 -0
- swarm/static/rest_mode/js/ui.js +265 -0
- swarm/static/rest_mode/js/validation.js +57 -0
- swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
- swarm/static/rest_mode/svg/arrow_down.svg +5 -0
- swarm/static/rest_mode/svg/arrow_left.svg +5 -0
- swarm/static/rest_mode/svg/arrow_right.svg +5 -0
- swarm/static/rest_mode/svg/arrow_up.svg +5 -0
- swarm/static/rest_mode/svg/attach.svg +8 -0
- swarm/static/rest_mode/svg/avatar.svg +7 -0
- swarm/static/rest_mode/svg/canvas.svg +6 -0
- swarm/static/rest_mode/svg/chat_history.svg +4 -0
- swarm/static/rest_mode/svg/close.svg +5 -0
- swarm/static/rest_mode/svg/copy.svg +4 -0
- swarm/static/rest_mode/svg/dark_mode.svg +3 -0
- swarm/static/rest_mode/svg/edit.svg +5 -0
- swarm/static/rest_mode/svg/layout.svg +9 -0
- swarm/static/rest_mode/svg/logo.svg +29 -0
- swarm/static/rest_mode/svg/logout.svg +5 -0
- swarm/static/rest_mode/svg/mobile.svg +5 -0
- swarm/static/rest_mode/svg/new_chat.svg +4 -0
- swarm/static/rest_mode/svg/not_visible.svg +5 -0
- swarm/static/rest_mode/svg/plus.svg +7 -0
- swarm/static/rest_mode/svg/run_code.svg +6 -0
- swarm/static/rest_mode/svg/save.svg +4 -0
- swarm/static/rest_mode/svg/search.svg +6 -0
- swarm/static/rest_mode/svg/settings.svg +4 -0
- swarm/static/rest_mode/svg/speaker.svg +5 -0
- swarm/static/rest_mode/svg/stop.svg +6 -0
- swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
- swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
- swarm/static/rest_mode/svg/toggle_off.svg +6 -0
- swarm/static/rest_mode/svg/toggle_on.svg +6 -0
- swarm/static/rest_mode/svg/trash.svg +10 -0
- swarm/static/rest_mode/svg/undo.svg +3 -0
- swarm/static/rest_mode/svg/visible.svg +8 -0
- swarm/static/rest_mode/svg/voice.svg +10 -0
- swarm/templates/account/login.html +22 -0
- swarm/templates/account/signup.html +32 -0
- swarm/templates/base.html +30 -0
- swarm/templates/chat.html +43 -0
- swarm/templates/index.html +35 -0
- swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
- swarm/templates/rest_mode/components/header.html +45 -0
- swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
- swarm/templates/rest_mode/components/settings_dialog.html +97 -0
- swarm/templates/rest_mode/components/splash_screen.html +7 -0
- swarm/templates/rest_mode/components/top_bar.html +28 -0
- swarm/templates/rest_mode/message_ui.html +50 -0
- swarm/templates/rest_mode/slackbot.html +30 -0
- swarm/templates/simple_blueprint_page.html +24 -0
- swarm/templates/websocket_partials/final_system_message.html +3 -0
- swarm/templates/websocket_partials/system_message.html +4 -0
- swarm/templates/websocket_partials/user_message.html +5 -0
- swarm/urls.py +57 -74
- swarm/utils/log_utils.py +63 -0
- swarm/views/api_views.py +48 -39
- swarm/views/chat_views.py +156 -70
- swarm/views/core_views.py +85 -90
- swarm/views/model_views.py +64 -121
- swarm/views/utils.py +65 -441
- open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
- open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
- open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
- open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
- swarm/agent/agent.py +0 -49
- swarm/core.py +0 -326
- swarm/extensions/mcp/__init__.py +0 -1
- swarm/extensions/mcp/cache_utils.py +0 -36
- swarm/extensions/mcp/mcp_client.py +0 -341
- swarm/extensions/mcp/mcp_constants.py +0 -7
- swarm/extensions/mcp/mcp_tool_provider.py +0 -110
- swarm/types.py +0 -126
- {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import json
|
5
|
+
import sqlite3 # Use standard sqlite3 module
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Dict, Any, List, ClassVar, Optional
|
8
|
+
|
9
|
+
# Ensure src is in path for BlueprintBase import
|
10
|
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
11
|
+
src_path = os.path.join(project_root, 'src')
|
12
|
+
if src_path not in sys.path: sys.path.insert(0, src_path)
|
13
|
+
|
14
|
+
try:
|
15
|
+
from agents import Agent, Tool, function_tool, Runner
|
16
|
+
from agents.mcp import MCPServer
|
17
|
+
from agents.models.interface import Model
|
18
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
19
|
+
from openai import AsyncOpenAI
|
20
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
21
|
+
except ImportError as e:
|
22
|
+
print(f"ERROR: Import failed in MissionImprobableBlueprint: {e}. Check dependencies.")
|
23
|
+
print(f"sys.path: {sys.path}")
|
24
|
+
sys.exit(1)
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
# --- Database Constants ---
|
29
|
+
# Using the same DB file as dilbot_universe
|
30
|
+
DB_FILE_NAME = "swarm_instructions.db"
|
31
|
+
DB_PATH = Path(project_root) / DB_FILE_NAME
|
32
|
+
TABLE_NAME = "agent_instructions" # agent_name TEXT PRIMARY KEY, instruction_text TEXT, model_profile TEXT
|
33
|
+
|
34
|
+
# --- Define the Blueprint ---
|
35
|
+
# Renamed class for consistency
|
36
|
+
class MissionImprobableBlueprint(BlueprintBase):
|
37
|
+
"""A cheeky team on a mission: led by JimFlimsy with support from CinnamonToast and RollinFumble."""
|
38
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
39
|
+
"name": "MissionImprobableBlueprint",
|
40
|
+
"title": "Mission: Improbable",
|
41
|
+
"description": "A cheeky team led by JimFlimsy (coordinator), CinnamonToast (strategist/filesystem), and RollinFumble (operative/shell). Uses SQLite for instructions.",
|
42
|
+
"version": "1.1.0", # Refactored version
|
43
|
+
"author": "Open Swarm Team (Refactored)",
|
44
|
+
"tags": ["comedy", "multi-agent", "filesystem", "shell", "sqlite"],
|
45
|
+
"required_mcp_servers": ["memory", "filesystem", "mcp-shell"], # Servers needed by the agents
|
46
|
+
"env_vars": ["ALLOWED_PATH"], # Informational: filesystem MCP likely needs this
|
47
|
+
}
|
48
|
+
|
49
|
+
# Caches
|
50
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
51
|
+
_model_instance_cache: Dict[str, Model] = {}
|
52
|
+
_db_initialized = False # Flag to ensure DB init runs only once per instance
|
53
|
+
|
54
|
+
# --- Database Interaction ---
|
55
|
+
def _init_db_and_load_data(self) -> None:
|
56
|
+
"""Initializes the SQLite DB, creates table, and loads sample data if needed."""
|
57
|
+
if self._db_initialized:
|
58
|
+
return
|
59
|
+
|
60
|
+
logger.info(f"Initializing SQLite database at: {DB_PATH} for Mission Improbable")
|
61
|
+
try:
|
62
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
63
|
+
with sqlite3.connect(DB_PATH) as conn:
|
64
|
+
cursor = conn.cursor()
|
65
|
+
# Ensure table exists (same table as dilbot)
|
66
|
+
cursor.execute(f"""
|
67
|
+
CREATE TABLE IF NOT EXISTS {TABLE_NAME} (
|
68
|
+
agent_name TEXT PRIMARY KEY,
|
69
|
+
instruction_text TEXT NOT NULL,
|
70
|
+
model_profile TEXT DEFAULT 'default'
|
71
|
+
)
|
72
|
+
""")
|
73
|
+
logger.debug(f"Table '{TABLE_NAME}' ensured in {DB_PATH}")
|
74
|
+
|
75
|
+
# Check if data for JimFlimsy needs loading
|
76
|
+
cursor.execute(f"SELECT COUNT(*) FROM {TABLE_NAME} WHERE agent_name = ?", ("JimFlimsy",))
|
77
|
+
count = cursor.fetchone()[0]
|
78
|
+
|
79
|
+
if count == 0:
|
80
|
+
logger.info(f"No instructions found for JimFlimsy in {DB_PATH}. Loading sample data...")
|
81
|
+
sample_instructions = [
|
82
|
+
("JimFlimsy",
|
83
|
+
("You’re JimFlimsy, the fearless leader:\n"
|
84
|
+
"1. Start with 'Syncing systems...' and use the `memory` MCP to load any relevant mission state (if available).\n"
|
85
|
+
"2. Understand the user's mission request.\n"
|
86
|
+
"3. Delegate strategic file management or planning tasks to CinnamonToast using the `CinnamonToast` agent tool.\n"
|
87
|
+
"4. Delegate command execution or operative tasks to RollinFumble using the `RollinFumble` agent tool.\n"
|
88
|
+
"5. Synthesize results from your agents and report back to the user. Log mission updates implicitly through conversation flow."),
|
89
|
+
"default"),
|
90
|
+
("CinnamonToast",
|
91
|
+
("You’re CinnamonToast, the quick-witted strategist:\n"
|
92
|
+
"1. Receive file management or strategic tasks from JimFlimsy.\n"
|
93
|
+
"2. Use the `filesystem` MCP tool to create, read, or delete files as requested.\n"
|
94
|
+
"3. Report the outcome of your actions clearly back to JimFlimsy."),
|
95
|
+
"default"), # Explicitly using default, could be different
|
96
|
+
("RollinFumble",
|
97
|
+
("You’re RollinFumble, the unpredictable operative:\n"
|
98
|
+
"1. Receive command execution tasks from JimFlimsy.\n"
|
99
|
+
"2. Use the `mcp-shell` MCP tool to execute the requested shell command. Be careful!\n"
|
100
|
+
"3. Summarize the output or result of the command and report back to JimFlimsy."),
|
101
|
+
"default")
|
102
|
+
]
|
103
|
+
cursor.executemany(f"INSERT OR IGNORE INTO {TABLE_NAME} (agent_name, instruction_text, model_profile) VALUES (?, ?, ?)", sample_instructions)
|
104
|
+
conn.commit()
|
105
|
+
logger.info(f"Sample agent instructions for Mission Improbable loaded into {DB_PATH}")
|
106
|
+
else:
|
107
|
+
logger.info(f"Mission Improbable agent instructions found in {DB_PATH}. Skipping sample data loading.")
|
108
|
+
|
109
|
+
self._db_initialized = True
|
110
|
+
|
111
|
+
except sqlite3.Error as e:
|
112
|
+
logger.error(f"SQLite error during DB initialization/loading: {e}", exc_info=True)
|
113
|
+
self._db_initialized = False
|
114
|
+
except Exception as e:
|
115
|
+
logger.error(f"Unexpected error during DB initialization/loading: {e}", exc_info=True)
|
116
|
+
self._db_initialized = False
|
117
|
+
|
118
|
+
def get_agent_config(self, agent_name: str) -> Dict[str, Any]:
|
119
|
+
"""Fetches agent config from SQLite DB or returns defaults."""
|
120
|
+
if self._db_initialized:
|
121
|
+
try:
|
122
|
+
with sqlite3.connect(DB_PATH) as conn:
|
123
|
+
conn.row_factory = sqlite3.Row
|
124
|
+
cursor = conn.cursor()
|
125
|
+
cursor.execute(f"SELECT instruction_text, model_profile FROM {TABLE_NAME} WHERE agent_name = ?", (agent_name,))
|
126
|
+
row = cursor.fetchone()
|
127
|
+
if row:
|
128
|
+
logger.debug(f"Loaded config for agent '{agent_name}' from SQLite.")
|
129
|
+
return {"instructions": row["instruction_text"], "model_profile": row["model_profile"] or "default"}
|
130
|
+
else:
|
131
|
+
logger.warning(f"No config found for agent '{agent_name}' in SQLite. Using defaults.")
|
132
|
+
except sqlite3.Error as e:
|
133
|
+
logger.error(f"SQLite error fetching config for '{agent_name}': {e}. Using defaults.", exc_info=True)
|
134
|
+
except Exception as e:
|
135
|
+
logger.error(f"Unexpected error fetching config for '{agent_name}': {e}. Using defaults.", exc_info=True)
|
136
|
+
|
137
|
+
# --- Fallback Hardcoded Defaults ---
|
138
|
+
logger.warning(f"Using hardcoded default config for agent '{agent_name}'.")
|
139
|
+
default_instructions = {
|
140
|
+
"JimFlimsy": "You are JimFlimsy, the leader. Delegate tasks. [Default - DB Failed]",
|
141
|
+
"CinnamonToast": "You are CinnamonToast, strategist. Use filesystem. [Default - DB Failed]",
|
142
|
+
"RollinFumble": "You are RollinFumble, operative. Use shell. [Default - DB Failed]",
|
143
|
+
}
|
144
|
+
return {
|
145
|
+
"instructions": default_instructions.get(agent_name, f"Default instructions for {agent_name}."),
|
146
|
+
"model_profile": "default",
|
147
|
+
}
|
148
|
+
|
149
|
+
# --- Model Instantiation Helper --- (Standard helper)
|
150
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
151
|
+
"""Retrieves or creates an LLM Model instance."""
|
152
|
+
# ... (Implementation is the same as previous refactors) ...
|
153
|
+
if profile_name in self._model_instance_cache:
|
154
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
155
|
+
return self._model_instance_cache[profile_name]
|
156
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
157
|
+
profile_data = self.get_llm_profile(profile_name)
|
158
|
+
if not profile_data:
|
159
|
+
logger.critical(f"LLM profile '{profile_name}' (or 'default') not found.")
|
160
|
+
raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
|
161
|
+
provider = profile_data.get("provider", "openai").lower()
|
162
|
+
model_name = profile_data.get("model")
|
163
|
+
if not model_name:
|
164
|
+
logger.critical(f"LLM profile '{profile_name}' missing 'model' key.")
|
165
|
+
raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
|
166
|
+
if provider != "openai":
|
167
|
+
logger.error(f"Unsupported LLM provider '{provider}'.")
|
168
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
169
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
170
|
+
if client_cache_key not in self._openai_client_cache:
|
171
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
172
|
+
filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
173
|
+
log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
|
174
|
+
logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
|
175
|
+
try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
|
176
|
+
except Exception as e: raise ValueError(f"Failed to init OpenAI client: {e}") from e
|
177
|
+
client = self._openai_client_cache[client_cache_key]
|
178
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
|
179
|
+
try:
|
180
|
+
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
|
181
|
+
self._model_instance_cache[profile_name] = model_instance
|
182
|
+
return model_instance
|
183
|
+
except Exception as e: raise ValueError(f"Failed to init LLM provider: {e}") from e
|
184
|
+
|
185
|
+
# --- Agent Creation ---
|
186
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
187
|
+
"""Creates the Mission Improbable agent team and returns JimFlimsy (Coordinator)."""
|
188
|
+
# Initialize DB and load data if needed
|
189
|
+
self._init_db_and_load_data()
|
190
|
+
|
191
|
+
logger.debug("Creating Mission Improbable agent team...")
|
192
|
+
self._model_instance_cache = {}
|
193
|
+
self._openai_client_cache = {}
|
194
|
+
|
195
|
+
# Helper to filter MCP servers
|
196
|
+
def get_agent_mcps(names: List[str]) -> List[MCPServer]:
|
197
|
+
return [s for s in mcp_servers if s.name in names]
|
198
|
+
|
199
|
+
# Create agents, fetching config and assigning MCPs
|
200
|
+
agents: Dict[str, Agent] = {}
|
201
|
+
for name in ["JimFlimsy", "CinnamonToast", "RollinFumble"]:
|
202
|
+
config = self.get_agent_config(name)
|
203
|
+
model_instance = self._get_model_instance(config["model_profile"])
|
204
|
+
agent_mcps = []
|
205
|
+
if name == "JimFlimsy": agent_mcps = get_agent_mcps(["memory"])
|
206
|
+
elif name == "CinnamonToast": agent_mcps = get_agent_mcps(["filesystem"])
|
207
|
+
elif name == "RollinFumble": agent_mcps = get_agent_mcps(["mcp-shell"])
|
208
|
+
|
209
|
+
agents[name] = Agent(
|
210
|
+
name=name,
|
211
|
+
instructions=config["instructions"],
|
212
|
+
model=model_instance,
|
213
|
+
tools=[], # Agent tools added to Jim below
|
214
|
+
mcp_servers=agent_mcps
|
215
|
+
)
|
216
|
+
|
217
|
+
# Add agent tools to the coordinator (JimFlimsy)
|
218
|
+
agents["JimFlimsy"].tools.extend([
|
219
|
+
agents["CinnamonToast"].as_tool(
|
220
|
+
tool_name="CinnamonToast",
|
221
|
+
tool_description="Delegate file management or strategic planning tasks."
|
222
|
+
),
|
223
|
+
agents["RollinFumble"].as_tool(
|
224
|
+
tool_name="RollinFumble",
|
225
|
+
tool_description="Delegate shell command execution tasks."
|
226
|
+
)
|
227
|
+
])
|
228
|
+
|
229
|
+
logger.debug("Mission Improbable agents created. Starting with JimFlimsy.")
|
230
|
+
return agents["JimFlimsy"] # Jim is the coordinator
|
231
|
+
|
232
|
+
# Standard Python entry point
|
233
|
+
if __name__ == "__main__":
|
234
|
+
MissionImprobableBlueprint.main()
|
@@ -0,0 +1,248 @@
|
|
1
|
+
"""
|
2
|
+
MonkaiMagic: Cloud Operations Journey Blueprint
|
3
|
+
|
4
|
+
A *Monkai Magic*-inspired crew managing AWS, Fly.io, and Vercel with pre-authenticated CLIs:
|
5
|
+
- Tripitaka (Wise Leader/Coordinator)
|
6
|
+
- Monkey (Cloud Trickster/AWS Master)
|
7
|
+
- Pigsy (Greedy Tinker/CLI Handler)
|
8
|
+
- Sandy (River Sage/Ops Watcher)
|
9
|
+
|
10
|
+
Uses BlueprintBase, @function_tool for direct CLI calls, and agent-as-tool delegation.
|
11
|
+
Assumes pre-authenticated aws, flyctl, and vercel commands.
|
12
|
+
"""
|
13
|
+
|
14
|
+
import os
|
15
|
+
import logging
|
16
|
+
import subprocess
|
17
|
+
import sys
|
18
|
+
import shlex # Import shlex
|
19
|
+
from typing import Dict, Any, List, ClassVar, Optional
|
20
|
+
|
21
|
+
# Ensure src is in path for BlueprintBase import
|
22
|
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
23
|
+
src_path = os.path.join(project_root, 'src')
|
24
|
+
if src_path not in sys.path: sys.path.insert(0, src_path)
|
25
|
+
|
26
|
+
try:
|
27
|
+
from agents import Agent, Tool, function_tool, Runner
|
28
|
+
from agents.mcp import MCPServer
|
29
|
+
from agents.models.interface import Model
|
30
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
31
|
+
from openai import AsyncOpenAI
|
32
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
33
|
+
except ImportError as e:
|
34
|
+
print(f"ERROR: Import failed in MonkaiMagicBlueprint: {e}. Check dependencies.")
|
35
|
+
print(f"sys.path: {sys.path}")
|
36
|
+
sys.exit(1)
|
37
|
+
|
38
|
+
logger = logging.getLogger(__name__)
|
39
|
+
|
40
|
+
# --- Cloud CLI Function Tools ---
|
41
|
+
@function_tool
|
42
|
+
def aws_cli(command: str) -> str:
|
43
|
+
"""Executes an AWS CLI command (e.g., 's3 ls', 'ec2 describe-instances'). Assumes pre-authentication."""
|
44
|
+
if not command: return "Error: No AWS command provided."
|
45
|
+
try:
|
46
|
+
# Avoid shell=True if possible, split command carefully
|
47
|
+
cmd_parts = ["aws"] + shlex.split(command)
|
48
|
+
logger.info(f"Executing AWS CLI: {' '.join(cmd_parts)}")
|
49
|
+
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=120)
|
50
|
+
output = result.stdout.strip()
|
51
|
+
logger.debug(f"AWS CLI success. Output:\n{output[:500]}...")
|
52
|
+
return f"OK: AWS command successful.\nOutput:\n{output}"
|
53
|
+
except FileNotFoundError:
|
54
|
+
logger.error("AWS CLI ('aws') command not found. Is it installed and in PATH?")
|
55
|
+
return "Error: AWS CLI command not found."
|
56
|
+
except subprocess.CalledProcessError as e:
|
57
|
+
error_output = e.stderr.strip() or e.stdout.strip()
|
58
|
+
logger.error(f"AWS CLI error executing '{command}': {error_output}")
|
59
|
+
return f"Error executing AWS command '{command}': {error_output}"
|
60
|
+
except subprocess.TimeoutExpired:
|
61
|
+
logger.error(f"AWS CLI command '{command}' timed out.")
|
62
|
+
return f"Error: AWS CLI command '{command}' timed out."
|
63
|
+
except Exception as e:
|
64
|
+
logger.error(f"Unexpected error during AWS CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
|
65
|
+
return f"Error: Unexpected error during AWS CLI: {e}"
|
66
|
+
|
67
|
+
@function_tool
|
68
|
+
def fly_cli(command: str) -> str:
|
69
|
+
"""Executes a Fly.io CLI command ('flyctl ...'). Assumes pre-authentication ('flyctl auth login')."""
|
70
|
+
if not command: return "Error: No Fly command provided."
|
71
|
+
try:
|
72
|
+
cmd_parts = ["flyctl"] + shlex.split(command)
|
73
|
+
logger.info(f"Executing Fly CLI: {' '.join(cmd_parts)}")
|
74
|
+
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=120)
|
75
|
+
output = result.stdout.strip()
|
76
|
+
logger.debug(f"Fly CLI success. Output:\n{output[:500]}...")
|
77
|
+
return f"OK: Fly command successful.\nOutput:\n{output}"
|
78
|
+
except FileNotFoundError:
|
79
|
+
logger.error("Fly CLI ('flyctl') command not found. Is it installed and in PATH?")
|
80
|
+
return "Error: Fly CLI command not found."
|
81
|
+
except subprocess.CalledProcessError as e:
|
82
|
+
error_output = e.stderr.strip() or e.stdout.strip()
|
83
|
+
logger.error(f"Fly CLI error executing '{command}': {error_output}")
|
84
|
+
return f"Error executing Fly command '{command}': {error_output}"
|
85
|
+
except subprocess.TimeoutExpired:
|
86
|
+
logger.error(f"Fly CLI command '{command}' timed out.")
|
87
|
+
return f"Error: Fly CLI command '{command}' timed out."
|
88
|
+
except Exception as e:
|
89
|
+
logger.error(f"Unexpected error during Fly CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
|
90
|
+
return f"Error: Unexpected error during Fly CLI: {e}"
|
91
|
+
|
92
|
+
@function_tool
|
93
|
+
def vercel_cli(command: str) -> str:
|
94
|
+
"""Executes a Vercel CLI command ('vercel ...'). Assumes pre-authentication ('vercel login')."""
|
95
|
+
if not command: return "Error: No Vercel command provided."
|
96
|
+
try:
|
97
|
+
cmd_parts = ["vercel"] + shlex.split(command)
|
98
|
+
logger.info(f"Executing Vercel CLI: {' '.join(cmd_parts)}")
|
99
|
+
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=120)
|
100
|
+
output = result.stdout.strip()
|
101
|
+
logger.debug(f"Vercel CLI success. Output:\n{output[:500]}...")
|
102
|
+
return f"OK: Vercel command successful.\nOutput:\n{output}"
|
103
|
+
except FileNotFoundError:
|
104
|
+
logger.error("Vercel CLI ('vercel') command not found. Is it installed and in PATH?")
|
105
|
+
return "Error: Vercel CLI command not found."
|
106
|
+
except subprocess.CalledProcessError as e:
|
107
|
+
error_output = e.stderr.strip() or e.stdout.strip()
|
108
|
+
logger.error(f"Vercel CLI error executing '{command}': {error_output}")
|
109
|
+
return f"Error executing Vercel command '{command}': {error_output}"
|
110
|
+
except subprocess.TimeoutExpired:
|
111
|
+
logger.error(f"Vercel CLI command '{command}' timed out.")
|
112
|
+
return f"Error: Vercel CLI command '{command}' timed out."
|
113
|
+
except Exception as e:
|
114
|
+
logger.error(f"Unexpected error during Vercel CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
|
115
|
+
return f"Error: Unexpected error during Vercel CLI: {e}"
|
116
|
+
|
117
|
+
|
118
|
+
# --- Define the Blueprint ---
|
119
|
+
class MonkaiMagicBlueprint(BlueprintBase):
|
120
|
+
"""Blueprint for a cloud operations team inspired by *Monkai Magic*."""
|
121
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
122
|
+
"name": "MonkaiMagicBlueprint",
|
123
|
+
"title": "MonkaiMagic: Cloud Operations Journey",
|
124
|
+
"description": "A *Monkai Magic*-inspired crew managing AWS, Fly.io, and Vercel with pre-authenticated CLI tools and agent-as-tool delegation.",
|
125
|
+
"version": "1.1.0", # Refactored version
|
126
|
+
"author": "Open Swarm Team (Refactored)",
|
127
|
+
"tags": ["cloud", "aws", "fly.io", "vercel", "cli", "multi-agent"],
|
128
|
+
"required_mcp_servers": ["mcp-shell"], # Only Sandy needs an MCP server
|
129
|
+
"env_vars": ["AWS_REGION", "FLY_REGION", "VERCEL_ORG_ID"] # Optional vars for instruction hints
|
130
|
+
}
|
131
|
+
|
132
|
+
# Caches
|
133
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
134
|
+
_model_instance_cache: Dict[str, Model] = {}
|
135
|
+
|
136
|
+
# --- Model Instantiation Helper --- (Standard helper)
|
137
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
138
|
+
"""Retrieves or creates an LLM Model instance."""
|
139
|
+
# ... (Implementation is the same as previous refactors) ...
|
140
|
+
if profile_name in self._model_instance_cache:
|
141
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
142
|
+
return self._model_instance_cache[profile_name]
|
143
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
144
|
+
profile_data = self.get_llm_profile(profile_name)
|
145
|
+
if not profile_data: raise ValueError(f"Missing LLM profile '{profile_name}'.")
|
146
|
+
provider = profile_data.get("provider", "openai").lower()
|
147
|
+
model_name = profile_data.get("model")
|
148
|
+
if not model_name: raise ValueError(f"Missing 'model' in profile '{profile_name}'.")
|
149
|
+
if provider != "openai": raise ValueError(f"Unsupported provider: {provider}")
|
150
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
151
|
+
if client_cache_key not in self._openai_client_cache:
|
152
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
153
|
+
filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
154
|
+
log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
|
155
|
+
logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
|
156
|
+
try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
|
157
|
+
except Exception as e: raise ValueError(f"Failed to init client: {e}") from e
|
158
|
+
client = self._openai_client_cache[client_cache_key]
|
159
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
|
160
|
+
try:
|
161
|
+
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
|
162
|
+
self._model_instance_cache[profile_name] = model_instance
|
163
|
+
return model_instance
|
164
|
+
except Exception as e: raise ValueError(f"Failed to init LLM: {e}") from e
|
165
|
+
|
166
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
167
|
+
"""Creates the MonkaiMagic agent team and returns Tripitaka."""
|
168
|
+
logger.debug("Creating MonkaiMagic agent team...")
|
169
|
+
self._model_instance_cache = {}
|
170
|
+
self._openai_client_cache = {}
|
171
|
+
|
172
|
+
default_profile_name = self.config.get("llm_profile", "default")
|
173
|
+
logger.debug(f"Using LLM profile '{default_profile_name}' for MonkaiMagic agents.")
|
174
|
+
model_instance = self._get_model_instance(default_profile_name)
|
175
|
+
|
176
|
+
# Get optional env var hints
|
177
|
+
aws_region = os.getenv("AWS_REGION")
|
178
|
+
fly_region = os.getenv("FLY_REGION")
|
179
|
+
vercel_org_id = os.getenv("VERCEL_ORG_ID")
|
180
|
+
|
181
|
+
# --- Define Agent Instructions (with optional hints) ---
|
182
|
+
tripitaka_instructions = (
|
183
|
+
"You are Tripitaka, the wise leader guiding the cloud journey:\n"
|
184
|
+
"- Lead with calm wisdom, analyzing user requests for cloud operations.\n"
|
185
|
+
"- Delegate tasks to the appropriate specialist agent using their Agent Tool:\n"
|
186
|
+
" - `Monkey`: For AWS related tasks (use the `aws_cli` function tool).\n"
|
187
|
+
" - `Pigsy`: For Fly.io or Vercel tasks (use `fly_cli` or `vercel_cli` function tools).\n"
|
188
|
+
" - `Sandy`: For monitoring or diagnostic shell commands related to deployments.\n"
|
189
|
+
"- Synthesize the results from your team into a final response for the user. You do not track state yourself."
|
190
|
+
)
|
191
|
+
|
192
|
+
monkey_instructions = (
|
193
|
+
"You are Monkey, the cloud trickster and AWS master:\n"
|
194
|
+
"- Execute AWS tasks requested by Tripitaka using the `aws_cli` function tool.\n"
|
195
|
+
"- Assume the `aws` command is pre-authenticated.\n"
|
196
|
+
f"- {f'Default AWS region seems to be {aws_region}. Use this unless specified otherwise.' if aws_region else 'No default AWS region hint available.'}\n"
|
197
|
+
"- Report the results (success or error) clearly back to Tripitaka."
|
198
|
+
)
|
199
|
+
|
200
|
+
pigsy_instructions = (
|
201
|
+
"You are Pigsy, the greedy tinker handling Fly.io and Vercel CLI hosting:\n"
|
202
|
+
"- Execute Fly.io tasks using the `fly_cli` function tool.\n"
|
203
|
+
"- Execute Vercel tasks using the `vercel_cli` function tool.\n"
|
204
|
+
"- Assume `flyctl` and `vercel` commands are pre-authenticated.\n"
|
205
|
+
f"- {f'Default Fly.io region hint: {fly_region}.' if fly_region else 'No default Fly.io region hint.'}\n"
|
206
|
+
f"- {f'Default Vercel Org ID hint: {vercel_org_id}.' if vercel_org_id else 'No default Vercel Org ID hint.'}\n"
|
207
|
+
"- Report the results clearly back to Tripitaka."
|
208
|
+
)
|
209
|
+
|
210
|
+
sandy_instructions = (
|
211
|
+
"You are Sandy, the river sage and ops watcher:\n"
|
212
|
+
"- Execute general shell commands requested by Tripitaka for monitoring or diagnostics using the `mcp-shell` MCP tool.\n"
|
213
|
+
"- Report the output or status steadily back to Tripitaka.\n"
|
214
|
+
"Available MCP Tools: mcp-shell."
|
215
|
+
)
|
216
|
+
|
217
|
+
# Instantiate agents
|
218
|
+
monkey_agent = Agent(
|
219
|
+
name="Monkey", model=model_instance, instructions=monkey_instructions,
|
220
|
+
tools=[aws_cli], # Function tool for AWS
|
221
|
+
mcp_servers=[]
|
222
|
+
)
|
223
|
+
pigsy_agent = Agent(
|
224
|
+
name="Pigsy", model=model_instance, instructions=pigsy_instructions,
|
225
|
+
tools=[fly_cli, vercel_cli], # Function tools for Fly/Vercel
|
226
|
+
mcp_servers=[]
|
227
|
+
)
|
228
|
+
sandy_agent = Agent(
|
229
|
+
name="Sandy", model=model_instance, instructions=sandy_instructions,
|
230
|
+
tools=[], # Uses MCP only
|
231
|
+
mcp_servers=[s for s in mcp_servers if s.name == 'mcp-shell'] # Pass only relevant MCP
|
232
|
+
)
|
233
|
+
tripitaka_agent = Agent(
|
234
|
+
name="Tripitaka", model=model_instance, instructions=tripitaka_instructions,
|
235
|
+
tools=[ # Delegate via Agent-as-Tool
|
236
|
+
monkey_agent.as_tool(tool_name="Monkey", tool_description="Delegate AWS tasks to Monkey."),
|
237
|
+
pigsy_agent.as_tool(tool_name="Pigsy", tool_description="Delegate Fly.io or Vercel tasks to Pigsy."),
|
238
|
+
sandy_agent.as_tool(tool_name="Sandy", tool_description="Delegate monitoring or diagnostic shell commands to Sandy.")
|
239
|
+
],
|
240
|
+
mcp_servers=[]
|
241
|
+
)
|
242
|
+
|
243
|
+
logger.debug("MonkaiMagic Team created. Starting with Tripitaka.")
|
244
|
+
return tripitaka_agent
|
245
|
+
|
246
|
+
# Standard Python entry point
|
247
|
+
if __name__ == "__main__":
|
248
|
+
MonkaiMagicBlueprint.main()
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import asyncio
|
5
|
+
import subprocess
|
6
|
+
import re
|
7
|
+
import inspect
|
8
|
+
from typing import Dict, Any, List, Optional, ClassVar
|
9
|
+
|
10
|
+
try:
|
11
|
+
from agents import Agent, Tool, function_tool
|
12
|
+
from agents.mcp import MCPServer
|
13
|
+
from agents.models.interface import Model
|
14
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
15
|
+
from openai import AsyncOpenAI
|
16
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
17
|
+
from rich.panel import Panel # Import Panel for splash screen
|
18
|
+
except ImportError as e:
|
19
|
+
print(f"ERROR: Import failed in nebula_shellz: {e}. Ensure 'openai-agents' install and structure.")
|
20
|
+
print(f"sys.path: {sys.path}")
|
21
|
+
sys.exit(1)
|
22
|
+
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
# --- Tool Definitions (Unchanged) ---
|
26
|
+
@function_tool
|
27
|
+
async def code_review(code_snippet: str) -> str:
|
28
|
+
"""Performs a review of the provided code snippet."""
|
29
|
+
logger.info(f"Reviewing code snippet: {code_snippet[:50]}...")
|
30
|
+
await asyncio.sleep(0.1); issues = []; ("TODO" in code_snippet and issues.append("Found TODO.")); (len(code_snippet.splitlines()) > 100 and issues.append("Code long.")); return "Review: " + " ".join(issues) if issues else "Code looks good!"
|
31
|
+
@function_tool
|
32
|
+
def generate_documentation(code_snippet: str) -> str:
|
33
|
+
"""Generates basic documentation string for the provided code snippet."""
|
34
|
+
logger.info(f"Generating documentation for: {code_snippet[:50]}...")
|
35
|
+
first_line = code_snippet.splitlines()[0] if code_snippet else "N/A"; doc = f"/**\n * This code snippet starts with: {first_line}...\n * TODO: Add more detailed documentation.\n */"; logger.debug(f"Generated documentation:\n{doc}"); return doc
|
36
|
+
@function_tool
|
37
|
+
def execute_shell_command(command: str) -> str:
|
38
|
+
"""Executes a shell command and returns its stdout and stderr."""
|
39
|
+
logger.info(f"Executing shell command: {command}")
|
40
|
+
if not command: logger.warning("execute_shell_command called with empty command."); return "Error: No command provided."
|
41
|
+
try:
|
42
|
+
result = subprocess.run(command, capture_output=True, text=True, timeout=60, check=False, shell=True); output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"; logger.debug(f"Command '{command}' result:\n{output}"); return output
|
43
|
+
except FileNotFoundError: cmd_base = command.split()[0] if command else ""; logger.error(f"Command not found: {cmd_base}"); return f"Error: Command not found - {cmd_base}"
|
44
|
+
except subprocess.TimeoutExpired: logger.error(f"Command '{command}' timed out after 60 seconds."); return f"Error: Command '{command}' timed out."
|
45
|
+
except Exception as e: logger.error(f"Error executing command '{command}': {e}", exc_info=logger.level <= logging.DEBUG); return f"Error executing command: {e}"
|
46
|
+
|
47
|
+
# --- Agent Definitions (Instructions remain the same) ---
|
48
|
+
morpheus_instructions = """
|
49
|
+
You are Morpheus, the leader... (Instructions as before) ...
|
50
|
+
"""
|
51
|
+
trinity_instructions = """
|
52
|
+
You are Trinity, the investigator... (Instructions as before) ...
|
53
|
+
"""
|
54
|
+
neo_instructions = """
|
55
|
+
You are Neo, the programmer... (Instructions as before) ...
|
56
|
+
"""
|
57
|
+
oracle_instructions = "You are the Oracle..."
|
58
|
+
cypher_instructions = "You are Cypher..."
|
59
|
+
tank_instructions = "You are Tank..."
|
60
|
+
|
61
|
+
# --- Blueprint Definition ---
|
62
|
+
class NebuchaShellzzarBlueprint(BlueprintBase):
|
63
|
+
"""A multi-agent blueprint inspired by The Matrix for sysadmin and coding tasks."""
|
64
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
65
|
+
"name": "NebulaShellzzarBlueprint", "title": "NebulaShellzzar",
|
66
|
+
"description": "A multi-agent blueprint inspired by The Matrix for system administration and coding tasks.",
|
67
|
+
"version": "1.0.0", "author": "Open Swarm Team",
|
68
|
+
"tags": ["matrix", "multi-agent", "shell", "coding", "mcp"],
|
69
|
+
"required_mcp_servers": ["memory"],
|
70
|
+
}
|
71
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
72
|
+
_model_instance_cache: Dict[str, Model] = {}
|
73
|
+
|
74
|
+
# --- ADDED: Splash Screen ---
|
75
|
+
def display_splash_screen(self):
|
76
|
+
"""Displays a Matrix-themed splash screen."""
|
77
|
+
splash_text = """
|
78
|
+
[bold green]Wake up, Neo...[/]
|
79
|
+
[green]The Matrix has you...[/]
|
80
|
+
[bold green]Follow the white rabbit.[/]
|
81
|
+
|
82
|
+
Initializing NebulaShellzzar Crew...
|
83
|
+
"""
|
84
|
+
panel = Panel(splash_text.strip(), title="[bold green]NebulaShellzzar[/]", border_style="green", expand=False)
|
85
|
+
self.console.print(panel)
|
86
|
+
self.console.print() # Add a blank line
|
87
|
+
|
88
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
89
|
+
"""Gets or creates a Model instance for the given profile name."""
|
90
|
+
if profile_name in self._model_instance_cache:
|
91
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
92
|
+
return self._model_instance_cache[profile_name]
|
93
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
94
|
+
profile_data = self.get_llm_profile(profile_name)
|
95
|
+
if not profile_data:
|
96
|
+
logger.critical(f"Cannot create Model instance: Profile '{profile_name}' (or default) not resolved.")
|
97
|
+
raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
|
98
|
+
provider = profile_data.get("provider", "openai").lower()
|
99
|
+
model_name = profile_data.get("model")
|
100
|
+
if not model_name:
|
101
|
+
logger.critical(f"LLM profile '{profile_name}' is missing the 'model' key.")
|
102
|
+
raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
|
103
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
104
|
+
if provider == "openai":
|
105
|
+
if client_cache_key not in self._openai_client_cache:
|
106
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
107
|
+
filtered_client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
108
|
+
log_client_kwargs = {k:v for k,v in filtered_client_kwargs.items() if k != 'api_key'}
|
109
|
+
logger.debug(f"Creating new AsyncOpenAI client for profile '{profile_name}' with config: {log_client_kwargs}") # Changed to DEBUG
|
110
|
+
try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_client_kwargs)
|
111
|
+
except Exception as e:
|
112
|
+
logger.error(f"Failed to create AsyncOpenAI client for profile '{profile_name}': {e}", exc_info=True)
|
113
|
+
raise ValueError(f"Failed to initialize OpenAI client for profile '{profile_name}': {e}") from e
|
114
|
+
openai_client_instance = self._openai_client_cache[client_cache_key]
|
115
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') with specific client instance.")
|
116
|
+
try: model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client_instance)
|
117
|
+
except Exception as e:
|
118
|
+
logger.error(f"Failed to instantiate OpenAIChatCompletionsModel for profile '{profile_name}': {e}", exc_info=True)
|
119
|
+
raise ValueError(f"Failed to initialize LLM provider for profile '{profile_name}': {e}") from e
|
120
|
+
else:
|
121
|
+
logger.error(f"Unsupported LLM provider '{provider}' in profile '{profile_name}'.")
|
122
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
123
|
+
self._model_instance_cache[profile_name] = model_instance
|
124
|
+
return model_instance
|
125
|
+
|
126
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
127
|
+
"""Creates the Matrix-themed agent team with Morpheus as the coordinator."""
|
128
|
+
logger.debug(f"Creating NebulaShellzzar agent team with {len(mcp_servers)} MCP server(s)...") # Changed to DEBUG
|
129
|
+
self._model_instance_cache = {}
|
130
|
+
self._openai_client_cache = {}
|
131
|
+
default_profile_name = self.config.get("llm_profile", "default")
|
132
|
+
default_model_instance = self._get_model_instance(default_profile_name)
|
133
|
+
logger.debug(f"Using LLM profile '{default_profile_name}' for all agents.") # Changed to DEBUG
|
134
|
+
|
135
|
+
neo = Agent(name="Neo", model=default_model_instance, instructions=neo_instructions, tools=[code_review, generate_documentation, execute_shell_command], mcp_servers=mcp_servers)
|
136
|
+
trinity = Agent(name="Trinity", model=default_model_instance, instructions=trinity_instructions, tools=[execute_shell_command], mcp_servers=mcp_servers)
|
137
|
+
oracle = Agent(name="Oracle", model=default_model_instance, instructions=oracle_instructions, tools=[])
|
138
|
+
cypher = Agent(name="Cypher", model=default_model_instance, instructions=cypher_instructions, tools=[execute_shell_command])
|
139
|
+
tank = Agent(name="Tank", model=default_model_instance, instructions=tank_instructions, tools=[execute_shell_command])
|
140
|
+
|
141
|
+
morpheus = Agent(
|
142
|
+
name="Morpheus", model=default_model_instance, instructions=morpheus_instructions,
|
143
|
+
tools=[
|
144
|
+
execute_shell_command,
|
145
|
+
neo.as_tool(tool_name="Neo", tool_description="Delegate coding, review, or documentation tasks to Neo."),
|
146
|
+
trinity.as_tool(tool_name="Trinity", tool_description="Delegate information gathering or reconnaissance shell commands to Trinity."),
|
147
|
+
cypher.as_tool(tool_name="Cypher", tool_description="Delegate tasks to Cypher for alternative perspectives or direct shell execution if needed."),
|
148
|
+
tank.as_tool(tool_name="Tank", tool_description="Delegate specific shell command execution to Tank."),
|
149
|
+
],
|
150
|
+
mcp_servers=mcp_servers
|
151
|
+
)
|
152
|
+
logger.debug("NebulaShellzzar agent team created. Morpheus is the starting agent.") # Changed to DEBUG
|
153
|
+
return morpheus
|
154
|
+
|
155
|
+
if __name__ == "__main__":
|
156
|
+
NebuchaShellzzarBlueprint.main()
|