open-swarm 0.1.1744967296__py3-none-any.whl → 0.1.1745017234__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.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/METADATA +126 -1
- {open_swarm-0.1.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/RECORD +16 -14
- swarm/blueprints/codey/blueprint_codey.py +209 -79
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +198 -29
- swarm/blueprints/echocraft/blueprint_echocraft.py +176 -3
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +258 -37
- swarm/blueprints/suggestion/blueprint_suggestion.py +175 -27
- swarm/core/blueprint_base.py +97 -33
- swarm/core/blueprint_ux.py +75 -0
- swarm/core/config_manager.py +30 -34
- swarm/core/output_utils.py +11 -11
- swarm/core/session_logger.py +42 -0
- swarm/core/slash_commands.py +45 -0
- {open_swarm-0.1.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/licenses/LICENSE +0 -0
swarm/core/blueprint_base.py
CHANGED
@@ -119,24 +119,23 @@ class BlueprintBase(ABC):
|
|
119
119
|
Defines the core interface for blueprint initialization and execution.
|
120
120
|
"""
|
121
121
|
enable_terminal_commands: bool = False # By default, terminal command execution is disabled
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
"""
|
126
|
-
Standard CLI entry point for all blueprints.
|
127
|
-
Subclasses can override metadata/config_path if needed.
|
128
|
-
"""
|
129
|
-
from swarm.extensions.blueprint.cli_handler import run_blueprint_cli
|
130
|
-
from pathlib import Path
|
131
|
-
swarm_version = getattr(cls, "SWARM_VERSION", "1.0.0")
|
132
|
-
config_path = getattr(cls, "DEFAULT_CONFIG_PATH", Path(__file__).parent / "swarm_config.json")
|
133
|
-
run_blueprint_cli(cls, swarm_version=swarm_version, default_config_path=config_path)
|
122
|
+
approval_required: bool = False
|
123
|
+
console = Console()
|
124
|
+
session_logger: 'SessionLogger' = None
|
134
125
|
|
135
126
|
def display_splash_screen(self, animated: bool = False):
|
136
127
|
"""Default splash screen. Subclasses can override for custom CLI/API branding."""
|
137
128
|
console = Console()
|
138
129
|
console.print(f"[bold cyan]Welcome to {self.__class__.__name__}![/]", style="bold")
|
139
130
|
|
131
|
+
def _load_configuration(self):
|
132
|
+
"""
|
133
|
+
Loads blueprint configuration. This method is a stub for compatibility with tests that patch it.
|
134
|
+
In production, configuration is loaded via _load_and_process_config.
|
135
|
+
"""
|
136
|
+
# You may override this in subclasses or patch in tests
|
137
|
+
return getattr(self, '_config', {})
|
138
|
+
|
140
139
|
def __init__(self, blueprint_id: str, config: dict = None, config_path: 'Optional[Path]' = None, enable_terminal_commands: 'Optional[bool]' = None, **kwargs):
|
141
140
|
try:
|
142
141
|
if not blueprint_id:
|
@@ -195,9 +194,14 @@ class BlueprintBase(ABC):
|
|
195
194
|
if _should_debug():
|
196
195
|
logger.warning(f"Falling back to CLI/home config due to error: {e}")
|
197
196
|
# 1. CLI argument (not handled here, handled in cli_handler)
|
198
|
-
# 2. Current working directory
|
199
|
-
|
200
|
-
|
197
|
+
# 2. Current working directory (guard against missing CWD)
|
198
|
+
try:
|
199
|
+
cwd_config = Path.cwd() / "swarm_config.json"
|
200
|
+
except Exception as e:
|
201
|
+
cwd_config = None
|
202
|
+
if _should_debug():
|
203
|
+
logger.warning(f"Unable to determine CWD for config lookup: {e}")
|
204
|
+
if cwd_config and cwd_config.exists():
|
201
205
|
with open(cwd_config, 'r') as f:
|
202
206
|
self._config = json.load(f)
|
203
207
|
# 3. XDG_CONFIG_HOME or ~/.config/swarm/swarm_config.json
|
@@ -344,30 +348,36 @@ class BlueprintBase(ABC):
|
|
344
348
|
if not hasattr(self, '_openai_client_cache'):
|
345
349
|
self._openai_client_cache = {}
|
346
350
|
if profile_name in self._model_instance_cache:
|
351
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
347
352
|
return self._model_instance_cache[profile_name]
|
353
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
348
354
|
profile_data = self.get_llm_profile(profile_name)
|
349
355
|
import os
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
356
|
+
# --- PATCH: API mode selection ---
|
357
|
+
# Default to 'completions' mode unless 'responses' is explicitly specified in swarm_config.json for this blueprint
|
358
|
+
api_mode = profile_data.get("api_mode") or self.config.get("api_mode") or "completions"
|
359
|
+
# Allow env override for debugging if needed
|
360
|
+
api_mode = os.getenv("SWARM_LLM_API_MODE", api_mode)
|
361
|
+
model_name = os.getenv("LITELLM_MODEL") or os.getenv("DEFAULT_LLM") or profile_data.get("model")
|
362
|
+
provider = profile_data.get("provider", "openai")
|
363
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
364
|
+
filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
365
|
+
log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
|
366
|
+
logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}' with {log_kwargs} and api_mode={api_mode}")
|
367
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}_{api_mode}"
|
359
368
|
if client_cache_key not in self._openai_client_cache:
|
360
|
-
|
361
|
-
|
362
|
-
except Exception as e:
|
363
|
-
raise ValueError(f"Failed to init client: {e}") from e
|
369
|
+
from openai import AsyncOpenAI
|
370
|
+
self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
|
364
371
|
client = self._openai_client_cache[client_cache_key]
|
365
|
-
|
372
|
+
# --- PATCH: Use correct model class based on api_mode ---
|
373
|
+
if api_mode == "responses":
|
374
|
+
from agents.models.openai_responses import OpenAIResponsesModel
|
375
|
+
model_instance = OpenAIResponsesModel(model=model_name, openai_client=client)
|
376
|
+
else:
|
377
|
+
from agents.models.openai_completions import OpenAIChatCompletionsModel
|
366
378
|
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
|
367
|
-
|
368
|
-
|
369
|
-
except Exception as e:
|
370
|
-
raise ValueError(f"Failed to init LLM: {e}") from e
|
379
|
+
self._model_instance_cache[profile_name] = model_instance
|
380
|
+
return model_instance
|
371
381
|
|
372
382
|
def make_agent(self, name, instructions, tools, mcp_servers=None, **kwargs):
|
373
383
|
"""Factory for creating an Agent with the correct model instance from framework config."""
|
@@ -382,6 +392,60 @@ class BlueprintBase(ABC):
|
|
382
392
|
**kwargs
|
383
393
|
)
|
384
394
|
|
395
|
+
def request_approval(self, action_type, action_summary, action_details=None):
|
396
|
+
"""
|
397
|
+
Prompt user for approval before executing an action.
|
398
|
+
Returns True if approved, False if rejected, or edited action if supported.
|
399
|
+
"""
|
400
|
+
try:
|
401
|
+
from swarm.core.blueprint_ux import BlueprintUX
|
402
|
+
ux = BlueprintUX(style="serious")
|
403
|
+
box = ux.box(f"Approve {action_type}?", action_summary, summary="Details:", params=action_details)
|
404
|
+
self.console.print(box)
|
405
|
+
except Exception:
|
406
|
+
print(f"Approve {action_type}?\n{action_summary}\nDetails: {action_details}")
|
407
|
+
while True:
|
408
|
+
resp = input("Approve this action? [y]es/[n]o/[e]dit/[s]kip: ").strip().lower()
|
409
|
+
if resp in ("y", "yes"): return True
|
410
|
+
if resp in ("n", "no"): return False
|
411
|
+
if resp in ("s", "skip"): return False
|
412
|
+
if resp in ("e", "edit"):
|
413
|
+
if action_details:
|
414
|
+
print("Edit not yet implemented; skipping.")
|
415
|
+
return False
|
416
|
+
else:
|
417
|
+
print("No editable content; skipping.")
|
418
|
+
return False
|
419
|
+
|
420
|
+
def execute_tool_with_approval(self, tool_func, action_type, action_summary, action_details=None, *args, **kwargs):
|
421
|
+
if getattr(self, 'approval_required', False):
|
422
|
+
approved = self.request_approval(action_type, action_summary, action_details)
|
423
|
+
if not approved:
|
424
|
+
try:
|
425
|
+
self.console.print(f"[yellow]Skipped {action_type}[/yellow]")
|
426
|
+
except Exception:
|
427
|
+
print(f"Skipped {action_type}")
|
428
|
+
return None
|
429
|
+
return tool_func(*args, **kwargs)
|
430
|
+
|
431
|
+
def start_session_logger(self, blueprint_name: str, global_instructions: str = None, project_instructions: str = None):
|
432
|
+
from swarm.core.session_logger import SessionLogger
|
433
|
+
self.session_logger = SessionLogger(blueprint_name=blueprint_name)
|
434
|
+
self.session_logger.log_instructions(global_instructions, project_instructions)
|
435
|
+
|
436
|
+
def log_message(self, role: str, content: str):
|
437
|
+
if self.session_logger:
|
438
|
+
self.session_logger.log_message(role, content)
|
439
|
+
|
440
|
+
def log_tool_call(self, tool_name: str, result: str):
|
441
|
+
if self.session_logger:
|
442
|
+
self.session_logger.log_tool_call(tool_name, result)
|
443
|
+
|
444
|
+
def close_session_logger(self):
|
445
|
+
if self.session_logger:
|
446
|
+
self.session_logger.close()
|
447
|
+
self.session_logger = None
|
448
|
+
|
385
449
|
@abstractmethod
|
386
450
|
async def run(self, messages: List[Dict[str, Any]], **kwargs: Any) -> AsyncGenerator[Dict[str, Any], None]:
|
387
451
|
"""
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import time
|
2
|
+
import itertools
|
3
|
+
|
4
|
+
# Style presets
|
5
|
+
def get_style(style):
|
6
|
+
if style == "serious":
|
7
|
+
return {
|
8
|
+
"border_top": "\033[1;34m╔" + "═"*50 + "╗\033[0m",
|
9
|
+
"border_bottom": "\033[1;34m╚" + "═"*50 + "╝\033[0m",
|
10
|
+
"border_side": "\033[1;34m║\033[0m",
|
11
|
+
"emoji": "🛠️",
|
12
|
+
"spinner": ['Generating.', 'Generating..', 'Generating...', 'Running...'],
|
13
|
+
"fallback": 'Generating... Taking longer than expected',
|
14
|
+
}
|
15
|
+
elif style == "silly":
|
16
|
+
return {
|
17
|
+
"border_top": "\033[1;35m(ノ◕ヮ◕)ノ*:・゚✧" + "~"*40 + "✧゚・: *ヽ(◕ヮ◕ヽ)\033[0m",
|
18
|
+
"border_bottom": "\033[1;35m(づ。◕‿‿◕。)づ" + "~"*40 + "づ(。◕‿‿◕。)づ\033[0m",
|
19
|
+
"border_side": "\033[1;35m~\033[0m",
|
20
|
+
"emoji": "🦆",
|
21
|
+
"spinner": ['Quacking.', 'Quacking..', 'Quacking...', 'Flapping...'],
|
22
|
+
"fallback": 'Quacking... Taking longer than expected',
|
23
|
+
}
|
24
|
+
else:
|
25
|
+
return get_style("serious")
|
26
|
+
|
27
|
+
class BlueprintUX:
|
28
|
+
def __init__(self, style="serious"):
|
29
|
+
self.style = style
|
30
|
+
self._style_conf = get_style(style)
|
31
|
+
self._spinner_cycle = itertools.cycle(self._style_conf["spinner"])
|
32
|
+
self._spinner_start = None
|
33
|
+
|
34
|
+
def box(self, title, content, summary=None, result_count=None, params=None):
|
35
|
+
lines = []
|
36
|
+
border_top = self._style_conf["border_top"]
|
37
|
+
border_bottom = self._style_conf["border_bottom"]
|
38
|
+
border_side = self._style_conf["border_side"]
|
39
|
+
emoji = self._style_conf["emoji"]
|
40
|
+
lines.append(border_top)
|
41
|
+
lines.append(f"{border_side} {emoji} {title:<46} {border_side}")
|
42
|
+
if summary:
|
43
|
+
lines.append(f"{border_side} {summary:<48} {border_side}")
|
44
|
+
if result_count is not None:
|
45
|
+
lines.append(f"{border_side} Results: {result_count:<41} {border_side}")
|
46
|
+
if params:
|
47
|
+
lines.append(f"{border_side} Params: {params:<41} {border_side}")
|
48
|
+
lines.append(f"{border_side}{'':<50}{border_side}")
|
49
|
+
for line in content.splitlines():
|
50
|
+
lines.append(f"{border_side} {line[:48]:<48} {border_side}")
|
51
|
+
lines.append(border_bottom)
|
52
|
+
return "\n".join(lines)
|
53
|
+
|
54
|
+
def spinner(self, state_idx, taking_long=False):
|
55
|
+
if taking_long:
|
56
|
+
return self._style_conf["fallback"]
|
57
|
+
spinner_states = self._style_conf["spinner"]
|
58
|
+
return spinner_states[state_idx % len(spinner_states)]
|
59
|
+
|
60
|
+
def summary(self, op_type, result_count, params):
|
61
|
+
return f"{op_type} | Results: {result_count} | Params: {params}"
|
62
|
+
|
63
|
+
def progress(self, current, total=None):
|
64
|
+
if total:
|
65
|
+
return f"Processed {current}/{total} lines..."
|
66
|
+
return f"Processed {current} lines..."
|
67
|
+
|
68
|
+
def code_vs_semantic(self, result_type, results):
|
69
|
+
if result_type == "code":
|
70
|
+
header = "[Code Search Results]"
|
71
|
+
elif result_type == "semantic":
|
72
|
+
header = "[Semantic Search Results]"
|
73
|
+
else:
|
74
|
+
header = "[Results]"
|
75
|
+
return f"{header}\n" + "\n".join(results)
|
swarm/core/config_manager.py
CHANGED
@@ -10,11 +10,7 @@ from swarm.core.server_config import load_server_config, save_server_config
|
|
10
10
|
from swarm.utils.color_utils import color_text
|
11
11
|
from swarm.settings import DEBUG
|
12
12
|
from swarm.core.utils.logger import *
|
13
|
-
from swarm.extensions.cli.utils import
|
14
|
-
prompt_user,
|
15
|
-
log_and_exit,
|
16
|
-
display_message
|
17
|
-
)
|
13
|
+
from swarm.extensions.cli.utils.prompt_user import prompt_user
|
18
14
|
|
19
15
|
# Initialize logger for this module
|
20
16
|
logger = logging.getLogger(__name__)
|
@@ -56,10 +52,10 @@ def backup_configuration(config_path: str) -> None:
|
|
56
52
|
try:
|
57
53
|
shutil.copy(config_path, backup_path)
|
58
54
|
logger.info(f"Configuration backup created at '{backup_path}'")
|
59
|
-
|
55
|
+
print(f"Backup of configuration created at '{backup_path}'")
|
60
56
|
except Exception as e:
|
61
57
|
logger.error(f"Failed to create configuration backup: {e}")
|
62
|
-
|
58
|
+
print(f"Failed to create backup: {e}")
|
63
59
|
sys.exit(1)
|
64
60
|
|
65
61
|
def load_config(config_path: str) -> Dict[str, Any]:
|
@@ -82,11 +78,11 @@ def load_config(config_path: str) -> Dict[str, Any]:
|
|
82
78
|
logger.debug(f"Raw configuration loaded: {config}")
|
83
79
|
except FileNotFoundError:
|
84
80
|
logger.error(f"Configuration file not found at {config_path}")
|
85
|
-
|
81
|
+
print(f"Configuration file not found at {config_path}")
|
86
82
|
sys.exit(1)
|
87
83
|
except json.JSONDecodeError as e:
|
88
84
|
logger.error(f"Invalid JSON in configuration file {config_path}: {e}")
|
89
|
-
|
85
|
+
print(f"Invalid JSON in configuration file {config_path}: {e}")
|
90
86
|
sys.exit(1)
|
91
87
|
|
92
88
|
# Resolve placeholders recursively
|
@@ -95,7 +91,7 @@ def load_config(config_path: str) -> Dict[str, Any]:
|
|
95
91
|
logger.debug(f"Configuration after resolving placeholders: {resolved_config}")
|
96
92
|
except Exception as e:
|
97
93
|
logger.error(f"Failed to resolve placeholders in configuration: {e}")
|
98
|
-
|
94
|
+
print(f"Failed to resolve placeholders in configuration: {e}")
|
99
95
|
sys.exit(1)
|
100
96
|
|
101
97
|
return resolved_config
|
@@ -115,10 +111,10 @@ def save_config(config_path: str, config: Dict[str, Any]) -> None:
|
|
115
111
|
with open(config_path, "w") as f:
|
116
112
|
json.dump(config, f, indent=4)
|
117
113
|
logger.info(f"Configuration saved to '{config_path}'")
|
118
|
-
|
114
|
+
print(f"Configuration saved to '{config_path}'")
|
119
115
|
except Exception as e:
|
120
116
|
logger.error(f"Failed to save configuration: {e}")
|
121
|
-
|
117
|
+
print(f"Failed to save configuration: {e}")
|
122
118
|
sys.exit(1)
|
123
119
|
|
124
120
|
def add_llm(config_path: str) -> None:
|
@@ -129,20 +125,20 @@ def add_llm(config_path: str) -> None:
|
|
129
125
|
config_path (str): Path to the configuration file.
|
130
126
|
"""
|
131
127
|
config = load_config(config_path)
|
132
|
-
|
128
|
+
print("Starting the process to add a new LLM.")
|
133
129
|
|
134
130
|
while True:
|
135
131
|
llm_name = prompt_user("Enter the name of the new LLM (or type 'done' to finish)").strip()
|
136
|
-
|
132
|
+
print(f"User entered LLM name: {llm_name}")
|
137
133
|
if llm_name.lower() == 'done':
|
138
|
-
|
134
|
+
print("Finished adding LLMs.")
|
139
135
|
break
|
140
136
|
if not llm_name:
|
141
|
-
|
137
|
+
print("LLM name cannot be empty.")
|
142
138
|
continue
|
143
139
|
|
144
140
|
if llm_name in config.get("llm", {}):
|
145
|
-
|
141
|
+
print(f"LLM '{llm_name}' already exists.")
|
146
142
|
continue
|
147
143
|
|
148
144
|
llm = {}
|
@@ -158,16 +154,16 @@ def add_llm(config_path: str) -> None:
|
|
158
154
|
temperature_input = prompt_user("Enter the temperature (e.g., 0.7)").strip()
|
159
155
|
llm["temperature"] = float(temperature_input)
|
160
156
|
except ValueError:
|
161
|
-
|
157
|
+
print("Invalid temperature value. Using default 0.7.")
|
162
158
|
llm["temperature"] = 0.7
|
163
159
|
|
164
160
|
config.setdefault("llm", {})[llm_name] = llm
|
165
161
|
logger.info(f"Added LLM '{llm_name}' to configuration.")
|
166
|
-
|
162
|
+
print(f"LLM '{llm_name}' added.")
|
167
163
|
|
168
164
|
backup_configuration(config_path)
|
169
165
|
save_config(config_path, config)
|
170
|
-
|
166
|
+
print("LLM configuration process completed.")
|
171
167
|
|
172
168
|
def remove_llm(config_path: str, llm_name: str) -> None:
|
173
169
|
"""
|
@@ -180,18 +176,18 @@ def remove_llm(config_path: str, llm_name: str) -> None:
|
|
180
176
|
config = load_config(config_path)
|
181
177
|
|
182
178
|
if llm_name not in config.get("llm", {}):
|
183
|
-
|
179
|
+
print(f"LLM '{llm_name}' does not exist.")
|
184
180
|
return
|
185
181
|
|
186
182
|
confirm = prompt_user(f"Are you sure you want to remove LLM '{llm_name}'? (yes/no)").strip().lower()
|
187
183
|
if confirm not in ['yes', 'y']:
|
188
|
-
|
184
|
+
print("Operation cancelled.")
|
189
185
|
return
|
190
186
|
|
191
187
|
del config["llm"][llm_name]
|
192
188
|
backup_configuration(config_path)
|
193
189
|
save_config(config_path, config)
|
194
|
-
|
190
|
+
print(f"LLM '{llm_name}' has been removed.")
|
195
191
|
logger.info(f"Removed LLM '{llm_name}' from configuration.")
|
196
192
|
|
197
193
|
def add_mcp_server(config_path: str) -> None:
|
@@ -202,20 +198,20 @@ def add_mcp_server(config_path: str) -> None:
|
|
202
198
|
config_path (str): Path to the configuration file.
|
203
199
|
"""
|
204
200
|
config = load_config(config_path)
|
205
|
-
|
201
|
+
print("Starting the process to add a new MCP server.")
|
206
202
|
|
207
203
|
while True:
|
208
204
|
server_name = prompt_user("Enter the name of the new MCP server (or type 'done' to finish)").strip()
|
209
|
-
|
205
|
+
print(f"User entered MCP server name: {server_name}")
|
210
206
|
if server_name.lower() == 'done':
|
211
|
-
|
207
|
+
print("Finished adding MCP servers.")
|
212
208
|
break
|
213
209
|
if not server_name:
|
214
|
-
|
210
|
+
print("Server name cannot be empty.")
|
215
211
|
continue
|
216
212
|
|
217
213
|
if server_name in config.get("mcpServers", {}):
|
218
|
-
|
214
|
+
print(f"MCP server '{server_name}' already exists.")
|
219
215
|
continue
|
220
216
|
|
221
217
|
server = {}
|
@@ -226,7 +222,7 @@ def add_mcp_server(config_path: str) -> None:
|
|
226
222
|
if not isinstance(server["args"], list):
|
227
223
|
raise ValueError
|
228
224
|
except ValueError:
|
229
|
-
|
225
|
+
print("Invalid arguments format. Using an empty list.")
|
230
226
|
server["args"] = []
|
231
227
|
|
232
228
|
env_vars = {}
|
@@ -242,11 +238,11 @@ def add_mcp_server(config_path: str) -> None:
|
|
242
238
|
|
243
239
|
config.setdefault("mcpServers", {})[server_name] = server
|
244
240
|
logger.info(f"Added MCP server '{server_name}' to configuration.")
|
245
|
-
|
241
|
+
print(f"MCP server '{server_name}' added.")
|
246
242
|
|
247
243
|
backup_configuration(config_path)
|
248
244
|
save_config(config_path, config)
|
249
|
-
|
245
|
+
print("MCP server configuration process completed.")
|
250
246
|
|
251
247
|
def remove_mcp_server(config_path: str, server_name: str) -> None:
|
252
248
|
"""
|
@@ -259,16 +255,16 @@ def remove_mcp_server(config_path: str, server_name: str) -> None:
|
|
259
255
|
config = load_config(config_path)
|
260
256
|
|
261
257
|
if server_name not in config.get("mcpServers", {}):
|
262
|
-
|
258
|
+
print(f"MCP server '{server_name}' does not exist.")
|
263
259
|
return
|
264
260
|
|
265
261
|
confirm = prompt_user(f"Are you sure you want to remove MCP server '{server_name}'? (yes/no)").strip().lower()
|
266
262
|
if confirm not in ['yes', 'y']:
|
267
|
-
|
263
|
+
print("Operation cancelled.")
|
268
264
|
return
|
269
265
|
|
270
266
|
del config["mcpServers"][server_name]
|
271
267
|
backup_configuration(config_path)
|
272
268
|
save_config(config_path, config)
|
273
|
-
|
269
|
+
print(f"MCP server '{server_name}' has been removed.")
|
274
270
|
logger.info(f"Removed MCP server '{server_name}' from configuration.")
|
swarm/core/output_utils.py
CHANGED
@@ -70,10 +70,10 @@ def ansi_box(title: str, content: str, color: str = "94", emoji: str = "🔎", b
|
|
70
70
|
def print_search_box(title: str, content: str, color: str = "94", emoji: str = "🔎"):
|
71
71
|
print(ansi_box(title, content, color=color, emoji=emoji))
|
72
72
|
|
73
|
-
def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = False, spinner=None) -> None:
|
74
|
-
"""Format and print messages, optionally rendering assistant content as markdown."""
|
73
|
+
def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = False, spinner=None, agent_name: str = None) -> None:
|
74
|
+
"""Format and print messages, optionally rendering assistant content as markdown, and always prefixing agent responses with the agent's name."""
|
75
75
|
# --- DEBUG PRINT ---
|
76
|
-
print(f"\n[DEBUG pretty_print_response called with {len(messages)} messages, use_markdown={use_markdown}]", flush=True)
|
76
|
+
print(f"\n[DEBUG pretty_print_response called with {len(messages)} messages, use_markdown={use_markdown}, agent_name={agent_name}]", flush=True)
|
77
77
|
|
78
78
|
if spinner:
|
79
79
|
spinner.stop()
|
@@ -85,7 +85,7 @@ def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = F
|
|
85
85
|
return
|
86
86
|
|
87
87
|
for i, msg in enumerate(messages):
|
88
|
-
|
88
|
+
# --- DEBUG PRINT ---
|
89
89
|
print(f"\n[DEBUG Processing message {i}: type={type(msg)}]", flush=True)
|
90
90
|
if not isinstance(msg, dict):
|
91
91
|
print(f"[DEBUG Skipping non-dict message {i}]", flush=True)
|
@@ -98,20 +98,20 @@ def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = F
|
|
98
98
|
# --- DEBUG PRINT ---
|
99
99
|
print(f"[DEBUG Message {i}: role={role}, sender={sender}, has_content={bool(msg_content)}, has_tools={bool(tool_calls)}]", flush=True)
|
100
100
|
|
101
|
-
|
102
101
|
if role == "assistant":
|
103
|
-
|
102
|
+
# Use agent_name if provided, else sender, else 'assistant'
|
103
|
+
display_name = agent_name or sender or "assistant"
|
104
|
+
# Magenta for agent output
|
105
|
+
print(f"\033[95m[{display_name}]\033[0m: ", end="", flush=True)
|
104
106
|
if msg_content:
|
105
|
-
|
107
|
+
# --- DEBUG PRINT ---
|
106
108
|
print(f"\n[DEBUG Assistant content found, printing/rendering... Rich={RICH_AVAILABLE}, Markdown={use_markdown}]", flush=True)
|
107
109
|
if use_markdown and RICH_AVAILABLE:
|
108
110
|
render_markdown(msg_content)
|
109
111
|
else:
|
110
|
-
|
111
|
-
print(f"\n[DEBUG Using standard print for content:]", flush=True)
|
112
|
-
print(msg_content, flush=True) # Added flush
|
112
|
+
print(msg_content, flush=True)
|
113
113
|
elif not tool_calls:
|
114
|
-
print(flush=True)
|
114
|
+
print(flush=True)
|
115
115
|
|
116
116
|
if tool_calls and isinstance(tool_calls, list):
|
117
117
|
print(" \033[92mTool Calls:\033[0m", flush=True)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import os
|
2
|
+
import datetime
|
3
|
+
from typing import Optional, List, Dict
|
4
|
+
|
5
|
+
class SessionLogger:
|
6
|
+
def __init__(self, blueprint_name: str, log_dir: Optional[str] = None):
|
7
|
+
if log_dir is None:
|
8
|
+
base_dir = os.path.dirname(__file__)
|
9
|
+
log_dir = os.path.join(base_dir, f"../blueprints/{blueprint_name}/session_logs")
|
10
|
+
os.makedirs(log_dir, exist_ok=True)
|
11
|
+
self.log_dir = log_dir
|
12
|
+
self.session_time = datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
|
13
|
+
self.log_path = os.path.join(log_dir, f"session_{self.session_time}.md")
|
14
|
+
self._open_log()
|
15
|
+
|
16
|
+
def _open_log(self):
|
17
|
+
self.log_file = open(self.log_path, "w")
|
18
|
+
self.log_file.write(f"# Session Log\n\nStarted: {self.session_time}\n\n")
|
19
|
+
self.log_file.flush()
|
20
|
+
|
21
|
+
def log_instructions(self, global_instructions: Optional[str], project_instructions: Optional[str]):
|
22
|
+
self.log_file.write("## Instructions\n")
|
23
|
+
if global_instructions:
|
24
|
+
self.log_file.write("### Global Instructions\n" + global_instructions + "\n\n")
|
25
|
+
if project_instructions:
|
26
|
+
self.log_file.write("### Project Instructions\n" + project_instructions + "\n\n")
|
27
|
+
self.log_file.write("## Messages\n")
|
28
|
+
self.log_file.flush()
|
29
|
+
|
30
|
+
def log_message(self, role: str, content: str, agent_name: str = None):
|
31
|
+
# Log agent name if provided, else fallback to role
|
32
|
+
display_name = agent_name or role
|
33
|
+
self.log_file.write(f"- **{display_name}**: {content}\n")
|
34
|
+
self.log_file.flush()
|
35
|
+
|
36
|
+
def log_tool_call(self, tool_name: str, result: str):
|
37
|
+
self.log_file.write(f"- **assistant (tool:{tool_name})**: {result}\n")
|
38
|
+
self.log_file.flush()
|
39
|
+
|
40
|
+
def close(self):
|
41
|
+
self.log_file.write(f"\nEnded: {datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')}\n")
|
42
|
+
self.log_file.close()
|
swarm/core/slash_commands.py
CHANGED
@@ -15,3 +15,48 @@ class SlashCommandRegistry:
|
|
15
15
|
return self.commands.get(command)
|
16
16
|
|
17
17
|
slash_registry = SlashCommandRegistry()
|
18
|
+
# Built-in '/help' slash command
|
19
|
+
@slash_registry.register('/help')
|
20
|
+
def _help_command(blueprint=None, args=None):
|
21
|
+
"""List available slash commands."""
|
22
|
+
cmds = sorted(slash_registry.commands.keys())
|
23
|
+
return "Available slash commands:\n" + "\n".join(cmds)
|
24
|
+
|
25
|
+
# Built-in '/compact' slash command
|
26
|
+
@slash_registry.register('/compact')
|
27
|
+
def _compact_command(blueprint=None, args=None):
|
28
|
+
"""Placeholder for compacting conversation context."""
|
29
|
+
return "[slash command] compact summary not implemented yet."
|
30
|
+
|
31
|
+
# Built-in '/model' slash command
|
32
|
+
@slash_registry.register('/model')
|
33
|
+
def _model_command(blueprint=None, args=None):
|
34
|
+
"""Show or switch the current LLM model."""
|
35
|
+
if args:
|
36
|
+
return f"[slash command] model switch not implemented. Requested: {args}"
|
37
|
+
profile = getattr(blueprint, 'llm_profile_name', None)
|
38
|
+
return f"[slash command] current LLM profile: {profile or 'unknown'}"
|
39
|
+
|
40
|
+
# Built-in '/approval' slash command
|
41
|
+
@slash_registry.register('/approval')
|
42
|
+
def _approval_command(blueprint=None, args=None):
|
43
|
+
"""Toggle or display auto-approval mode."""
|
44
|
+
return "[slash command] approval mode not implemented yet."
|
45
|
+
|
46
|
+
# Built-in '/history' slash command
|
47
|
+
@slash_registry.register('/history')
|
48
|
+
def _history_command(blueprint=None, args=None):
|
49
|
+
"""Display session history of commands and files."""
|
50
|
+
return "[slash command] history not implemented yet."
|
51
|
+
|
52
|
+
# Built-in '/clear' slash command
|
53
|
+
@slash_registry.register('/clear')
|
54
|
+
def _clear_command(blueprint=None, args=None):
|
55
|
+
"""Clear the screen and current context."""
|
56
|
+
return "[slash command] context cleared."
|
57
|
+
|
58
|
+
# Built-in '/clearhistory' slash command
|
59
|
+
@slash_registry.register('/clearhistory')
|
60
|
+
def _clearhistory_command(blueprint=None, args=None):
|
61
|
+
"""Clear the command history."""
|
62
|
+
return "[slash command] command history cleared."
|
File without changes
|
{open_swarm-0.1.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/entry_points.txt
RENAMED
File without changes
|
{open_swarm-0.1.1744967296.dist-info → open_swarm-0.1.1745017234.dist-info}/licenses/LICENSE
RENAMED
File without changes
|