open-swarm 0.1.1745126154__py3-none-any.whl → 0.1.1745126277__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.1745126154.dist-info → open_swarm-0.1.1745126277.dist-info}/METADATA +1 -1
- {open_swarm-0.1.1745126154.dist-info → open_swarm-0.1.1745126277.dist-info}/RECORD +52 -25
- swarm/blueprints/README.md +19 -18
- swarm/blueprints/blueprint_audit_status.json +1 -1
- swarm/blueprints/chatbot/blueprint_chatbot.py +160 -72
- swarm/blueprints/codey/README.md +88 -8
- swarm/blueprints/codey/blueprint_codey.py +1116 -210
- swarm/blueprints/codey/codey_cli.py +10 -0
- swarm/blueprints/codey/session_logs/session_2025-04-19T01-15-31.md +17 -0
- swarm/blueprints/codey/session_logs/session_2025-04-19T01-16-03.md +17 -0
- swarm/blueprints/common/operation_box_utils.py +83 -0
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +21 -298
- swarm/blueprints/divine_code/blueprint_divine_code.py +182 -9
- swarm/blueprints/django_chat/blueprint_django_chat.py +150 -24
- swarm/blueprints/echocraft/blueprint_echocraft.py +142 -13
- swarm/blueprints/geese/README.md +97 -0
- swarm/blueprints/geese/blueprint_geese.py +677 -93
- swarm/blueprints/geese/geese_cli.py +102 -0
- swarm/blueprints/jeeves/blueprint_jeeves.py +712 -0
- swarm/blueprints/jeeves/jeeves_cli.py +55 -0
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +109 -22
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +172 -40
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +79 -41
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +82 -35
- swarm/blueprints/omniplex/blueprint_omniplex.py +56 -24
- swarm/blueprints/poets/blueprint_poets.py +141 -100
- swarm/blueprints/poets/poets_cli.py +23 -0
- swarm/blueprints/rue_code/README.md +8 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +188 -20
- swarm/blueprints/rue_code/rue_code_cli.py +43 -0
- swarm/blueprints/stewie/apps.py +12 -0
- swarm/blueprints/stewie/blueprint_family_ties.py +349 -0
- swarm/blueprints/stewie/models.py +19 -0
- swarm/blueprints/stewie/serializers.py +10 -0
- swarm/blueprints/stewie/settings.py +17 -0
- swarm/blueprints/stewie/urls.py +11 -0
- swarm/blueprints/stewie/views.py +26 -0
- swarm/blueprints/suggestion/blueprint_suggestion.py +54 -39
- swarm/blueprints/whinge_surf/README.md +22 -0
- swarm/blueprints/whinge_surf/__init__.py +1 -0
- swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +565 -0
- swarm/blueprints/whinge_surf/whinge_surf_cli.py +99 -0
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +66 -37
- swarm/blueprints/zeus/__init__.py +2 -0
- swarm/blueprints/zeus/apps.py +4 -0
- swarm/blueprints/zeus/blueprint_zeus.py +270 -0
- swarm/blueprints/zeus/zeus_cli.py +13 -0
- swarm/cli/async_input.py +65 -0
- swarm/cli/async_input_demo.py +32 -0
- {open_swarm-0.1.1745126154.dist-info → open_swarm-0.1.1745126277.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1745126154.dist-info → open_swarm-0.1.1745126277.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1745126154.dist-info → open_swarm-0.1.1745126277.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
import argparse
|
2
|
+
import asyncio
|
3
|
+
from swarm.blueprints.jeeves.blueprint_jeeves import JeevesBlueprint, JeevesSpinner, display_operation_box, SPINNER_STATES
|
4
|
+
|
5
|
+
def main():
|
6
|
+
parser = argparse.ArgumentParser(description="Jeeves: Home automation and web search butler")
|
7
|
+
parser.add_argument("--instruction", type=str, help="Instruction for Jeeves to execute", default=None)
|
8
|
+
parser.add_argument("--message", dest='instruction', type=str, help="Instruction for Jeeves agent (alias --message)")
|
9
|
+
args = parser.parse_args()
|
10
|
+
bp = JeevesBlueprint(blueprint_id="jeeves")
|
11
|
+
|
12
|
+
async def run_instruction(instruction):
|
13
|
+
spinner = JeevesSpinner()
|
14
|
+
spinner.start()
|
15
|
+
try:
|
16
|
+
messages = [{"role": "user", "content": instruction}]
|
17
|
+
spinner_idx = 0
|
18
|
+
import time
|
19
|
+
spinner_start = time.time()
|
20
|
+
async for chunk in bp.run(messages):
|
21
|
+
if isinstance(chunk, dict) and (chunk.get("progress") or chunk.get("matches")):
|
22
|
+
elapsed = time.time() - spinner_start
|
23
|
+
spinner_state = spinner.current_spinner_state()
|
24
|
+
display_operation_box(
|
25
|
+
title="Progressive Operation",
|
26
|
+
content="\n".join(chunk.get("matches", [])),
|
27
|
+
style="bold cyan" if chunk.get("type") == "code_search" else "bold magenta",
|
28
|
+
result_count=len(chunk.get('matches', [])) if chunk.get("matches") is not None else None,
|
29
|
+
params={k: v for k, v in chunk.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
30
|
+
progress_line=chunk.get('progress'),
|
31
|
+
total_lines=chunk.get('total'),
|
32
|
+
spinner_state=spinner_state,
|
33
|
+
op_type=chunk.get("type", "search"),
|
34
|
+
emoji="🔍" if chunk.get("type") == "code_search" else "🧠"
|
35
|
+
)
|
36
|
+
else:
|
37
|
+
print(chunk)
|
38
|
+
finally:
|
39
|
+
spinner.stop()
|
40
|
+
|
41
|
+
if args.instruction:
|
42
|
+
messages = [{"role": "user", "content": args.instruction}]
|
43
|
+
asyncio.run(run_instruction(args.instruction))
|
44
|
+
else:
|
45
|
+
print("[Jeeves CLI] Type your instruction and press Enter. Ctrl+C to exit.")
|
46
|
+
try:
|
47
|
+
while True:
|
48
|
+
user_input = input("You: ")
|
49
|
+
if user_input.strip():
|
50
|
+
asyncio.run(run_instruction(user_input.strip()))
|
51
|
+
except (KeyboardInterrupt, EOFError):
|
52
|
+
print("\nExiting Jeeves CLI.")
|
53
|
+
|
54
|
+
if __name__ == "__main__":
|
55
|
+
main()
|
@@ -148,34 +148,108 @@ def list_files(directory: str = '.') -> str:
|
|
148
148
|
return f"ERROR: {e}"
|
149
149
|
def execute_shell_command(command: str) -> str:
|
150
150
|
"""
|
151
|
-
|
152
|
-
|
153
|
-
Returns a JSON mapping of command to its combined stdout and stderr.
|
151
|
+
Executes a shell command and returns its stdout and stderr.
|
152
|
+
Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
|
154
153
|
"""
|
155
|
-
|
154
|
+
logger.info(f"Executing shell command: {command}")
|
156
155
|
try:
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
outputs[cmd] = f"ERROR: {e}"
|
171
|
-
return json.dumps(outputs)
|
156
|
+
import os
|
157
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
|
158
|
+
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
|
159
|
+
output = f"Exit Code: {result.returncode}\n"
|
160
|
+
if result.stdout:
|
161
|
+
output += f"STDOUT:\n{result.stdout}\n"
|
162
|
+
if result.stderr:
|
163
|
+
output += f"STDERR:\n{result.stderr}\n"
|
164
|
+
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
165
|
+
return output.strip()
|
166
|
+
except subprocess.TimeoutExpired:
|
167
|
+
logger.error(f"Command timed out: {command}")
|
168
|
+
return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
|
172
169
|
except Exception as e:
|
173
|
-
|
170
|
+
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
171
|
+
return f"Error executing command: {e}"
|
174
172
|
read_file_tool = PatchedFunctionTool(read_file, 'read_file')
|
175
173
|
write_file_tool = PatchedFunctionTool(write_file, 'write_file')
|
176
174
|
list_files_tool = PatchedFunctionTool(list_files, 'list_files')
|
177
175
|
execute_shell_command_tool = PatchedFunctionTool(execute_shell_command, 'execute_shell_command')
|
178
176
|
|
177
|
+
# --- Spinner and ANSI/emoji operation box for unified UX (for CLI/dev runs) ---
|
178
|
+
from swarm.ux.ansi_box import ansi_box
|
179
|
+
from rich.console import Console
|
180
|
+
from rich.style import Style
|
181
|
+
from rich.text import Text
|
182
|
+
import threading
|
183
|
+
import time
|
184
|
+
|
185
|
+
class MCPDemoSpinner:
|
186
|
+
FRAMES = [
|
187
|
+
"Generating.", "Generating..", "Generating...", "Running...",
|
188
|
+
"⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
|
189
|
+
"⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
|
190
|
+
"⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
|
191
|
+
]
|
192
|
+
SLOW_FRAME = "Generating... Taking longer than expected"
|
193
|
+
INTERVAL = 0.12
|
194
|
+
SLOW_THRESHOLD = 10 # seconds
|
195
|
+
|
196
|
+
def __init__(self):
|
197
|
+
self._stop_event = threading.Event()
|
198
|
+
self._thread = None
|
199
|
+
self._start_time = None
|
200
|
+
self.console = Console()
|
201
|
+
self._last_frame = None
|
202
|
+
self._last_slow = False
|
203
|
+
|
204
|
+
def start(self):
|
205
|
+
self._stop_event.clear()
|
206
|
+
self._start_time = time.time()
|
207
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
208
|
+
self._thread.start()
|
209
|
+
|
210
|
+
def _spin(self):
|
211
|
+
idx = 0
|
212
|
+
while not self._stop_event.is_set():
|
213
|
+
elapsed = time.time() - self._start_time
|
214
|
+
if elapsed > self.SLOW_THRESHOLD:
|
215
|
+
txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
|
216
|
+
self._last_frame = self.SLOW_FRAME
|
217
|
+
self._last_slow = True
|
218
|
+
else:
|
219
|
+
frame = self.FRAMES[idx % len(self.FRAMES)]
|
220
|
+
txt = Text(frame, style=Style(color="cyan", bold=True))
|
221
|
+
self._last_frame = frame
|
222
|
+
self._last_slow = False
|
223
|
+
self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
|
224
|
+
time.sleep(self.INTERVAL)
|
225
|
+
idx += 1
|
226
|
+
self.console.print(" " * 40, end="\r") # Clear line
|
227
|
+
|
228
|
+
def stop(self, final_message="Done!"):
|
229
|
+
self._stop_event.set()
|
230
|
+
if self._thread:
|
231
|
+
self._thread.join()
|
232
|
+
self.console.print(Text(final_message, style=Style(color="green", bold=True)))
|
233
|
+
|
234
|
+
def current_spinner_state(self):
|
235
|
+
if self._last_slow:
|
236
|
+
return self.SLOW_FRAME
|
237
|
+
return self._last_frame or self.FRAMES[0]
|
238
|
+
|
239
|
+
|
240
|
+
def print_operation_box(op_type, results, params=None, result_type="mcp", taking_long=False):
|
241
|
+
emoji = "🧠" if result_type == "mcp" else "🔍"
|
242
|
+
style = 'success' if result_type == "mcp" else 'default'
|
243
|
+
box_title = op_type if op_type else ("MCPDemo Output" if result_type == "mcp" else "Results")
|
244
|
+
summary_lines = []
|
245
|
+
count = len(results) if isinstance(results, list) else 0
|
246
|
+
summary_lines.append(f"Results: {count}")
|
247
|
+
if params:
|
248
|
+
for k, v in params.items():
|
249
|
+
summary_lines.append(f"{k.capitalize()}: {v}")
|
250
|
+
box_content = "\n".join(summary_lines + ["\n".join(map(str, results))])
|
251
|
+
ansi_box(box_title, box_content, count=count, params=params, style=style if not taking_long else 'warning', emoji=emoji)
|
252
|
+
|
179
253
|
# --- Define the Blueprint ---
|
180
254
|
class MCPDemoBlueprint(BlueprintBase):
|
181
255
|
"""Demonstrates using filesystem and memory MCP servers."""
|
@@ -381,6 +455,19 @@ if __name__ == "__main__":
|
|
381
455
|
]
|
382
456
|
blueprint = MCPDemoBlueprint(blueprint_id="demo-1")
|
383
457
|
async def run_and_print():
|
384
|
-
|
385
|
-
|
458
|
+
spinner = MCPDemoSpinner()
|
459
|
+
spinner.start()
|
460
|
+
try:
|
461
|
+
all_results = []
|
462
|
+
async for response in blueprint.run(messages):
|
463
|
+
content = response["messages"][0]["content"]
|
464
|
+
all_results.append(content)
|
465
|
+
finally:
|
466
|
+
spinner.stop()
|
467
|
+
print_operation_box(
|
468
|
+
op_type="MCPDemo Output",
|
469
|
+
results=all_results,
|
470
|
+
params={"prompt": messages[0]["content"]},
|
471
|
+
result_type="mcp"
|
472
|
+
)
|
386
473
|
asyncio.run(run_and_print())
|
@@ -28,6 +28,7 @@ try:
|
|
28
28
|
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
29
29
|
from openai import AsyncOpenAI
|
30
30
|
from swarm.core.blueprint_base import BlueprintBase
|
31
|
+
from swarm.core.blueprint_ux import BlueprintUXImproved
|
31
32
|
except ImportError as e:
|
32
33
|
print(f"ERROR: Import failed in MissionImprobableBlueprint: {e}. Check dependencies.")
|
33
34
|
print(f"sys.path: {sys.path}")
|
@@ -60,12 +61,28 @@ def list_files(directory: str = '.') -> str:
|
|
60
61
|
except Exception as e:
|
61
62
|
return f"ERROR: {e}"
|
62
63
|
def execute_shell_command(command: str) -> str:
|
63
|
-
|
64
|
+
"""
|
65
|
+
Executes a shell command and returns its stdout and stderr.
|
66
|
+
Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
|
67
|
+
"""
|
68
|
+
logger.info(f"Executing shell command: {command}")
|
64
69
|
try:
|
65
|
-
|
66
|
-
|
70
|
+
import os
|
71
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
|
72
|
+
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
|
73
|
+
output = f"Exit Code: {result.returncode}\n"
|
74
|
+
if result.stdout:
|
75
|
+
output += f"STDOUT:\n{result.stdout}\n"
|
76
|
+
if result.stderr:
|
77
|
+
output += f"STDERR:\n{result.stderr}\n"
|
78
|
+
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
79
|
+
return output.strip()
|
80
|
+
except subprocess.TimeoutExpired:
|
81
|
+
logger.error(f"Command timed out: {command}")
|
82
|
+
return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
|
67
83
|
except Exception as e:
|
68
|
-
|
84
|
+
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
85
|
+
return f"Error executing command: {e}"
|
69
86
|
read_file_tool = PatchedFunctionTool(read_file, 'read_file')
|
70
87
|
write_file_tool = PatchedFunctionTool(write_file, 'write_file')
|
71
88
|
list_files_tool = PatchedFunctionTool(list_files, 'list_files')
|
@@ -100,18 +117,16 @@ class MissionImprobableBlueprint(BlueprintBase):
|
|
100
117
|
_model_instance_cache: Dict[str, Model] = {}
|
101
118
|
_db_initialized = False # Flag to ensure DB init runs only once per instance
|
102
119
|
|
103
|
-
def __init__(self, blueprint_id: str = None, config_path
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
return DummyStream()
|
114
|
-
self.llm = DummyLLM()
|
120
|
+
def __init__(self, blueprint_id: str = "mission_improbable", config=None, config_path=None, **kwargs):
|
121
|
+
super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
|
122
|
+
self.blueprint_id = blueprint_id
|
123
|
+
self.config_path = config_path
|
124
|
+
self._config = config if config is not None else None
|
125
|
+
self._llm_profile_name = None
|
126
|
+
self._llm_profile_data = None
|
127
|
+
self._markdown_output = None
|
128
|
+
# Add other attributes as needed for MissionImprobable
|
129
|
+
# ...
|
115
130
|
|
116
131
|
# --- Database Interaction ---
|
117
132
|
def _init_db_and_load_data(self) -> None:
|
@@ -252,29 +267,133 @@ class MissionImprobableBlueprint(BlueprintBase):
|
|
252
267
|
logger.debug("Mission Improbable agents created. Starting with JimFlimsy.")
|
253
268
|
return agents["JimFlimsy"] # Jim is the coordinator
|
254
269
|
|
255
|
-
async def run(self, messages: list)
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
270
|
+
async def run(self, messages: list, **kwargs):
|
271
|
+
"""Main execution entry point for the MissionImprobable blueprint."""
|
272
|
+
logger.info("MissionImprobableBlueprint run method called.")
|
273
|
+
instruction = messages[-1].get("content", "") if messages else ""
|
274
|
+
from agents import Runner
|
275
|
+
ux = BlueprintUXImproved(style="serious")
|
276
|
+
spinner_idx = 0
|
277
|
+
start_time = time.time()
|
278
|
+
spinner_yield_interval = 1.0 # seconds
|
279
|
+
last_spinner_time = start_time
|
280
|
+
yielded_spinner = False
|
281
|
+
result_chunks = []
|
282
|
+
try:
|
283
|
+
runner_gen = Runner.run(self.create_starting_agent([]), instruction)
|
284
|
+
while True:
|
285
|
+
now = time.time()
|
286
|
+
try:
|
287
|
+
chunk = next(runner_gen)
|
288
|
+
result_chunks.append(chunk)
|
289
|
+
# If chunk is a final result, wrap and yield
|
290
|
+
if chunk and isinstance(chunk, dict) and "messages" in chunk:
|
291
|
+
content = chunk["messages"][0]["content"] if chunk["messages"] else ""
|
292
|
+
summary = ux.summary("Operation", len(result_chunks), {"instruction": instruction[:40]})
|
293
|
+
box = ux.ansi_emoji_box(
|
294
|
+
title="MissionImprobable Result",
|
295
|
+
content=content,
|
296
|
+
summary=summary,
|
297
|
+
params={"instruction": instruction[:40]},
|
298
|
+
result_count=len(result_chunks),
|
299
|
+
op_type="run",
|
300
|
+
status="success"
|
301
|
+
)
|
302
|
+
yield {"messages": [{"role": "assistant", "content": box}]}
|
303
|
+
else:
|
304
|
+
yield chunk
|
305
|
+
yielded_spinner = False
|
306
|
+
except StopIteration:
|
307
|
+
break
|
308
|
+
except Exception:
|
309
|
+
if now - last_spinner_time >= spinner_yield_interval:
|
310
|
+
taking_long = (now - start_time > 10)
|
311
|
+
spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
|
312
|
+
yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
|
313
|
+
spinner_idx += 1
|
314
|
+
last_spinner_time = now
|
315
|
+
yielded_spinner = True
|
316
|
+
if not result_chunks and not yielded_spinner:
|
317
|
+
yield {"messages": [{"role": "assistant", "content": ux.spinner(0)}]}
|
318
|
+
except Exception as e:
|
319
|
+
logger.error(f"Error during MissionImprobable run: {e}", exc_info=True)
|
320
|
+
yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
|
275
321
|
|
276
|
-
|
277
|
-
|
322
|
+
# --- Spinner and ANSI/emoji operation box for unified UX (for CLI/dev runs) ---
|
323
|
+
from swarm.ux.ansi_box import ansi_box
|
324
|
+
from rich.console import Console
|
325
|
+
from rich.style import Style
|
326
|
+
from rich.text import Text
|
327
|
+
import threading
|
328
|
+
import time
|
329
|
+
|
330
|
+
class MissionImprobableSpinner:
|
331
|
+
FRAMES = [
|
332
|
+
"Generating.", "Generating..", "Generating...", "Running...",
|
333
|
+
"⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
|
334
|
+
"⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
|
335
|
+
"⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
|
336
|
+
]
|
337
|
+
SLOW_FRAME = "Generating... Taking longer than expected"
|
338
|
+
INTERVAL = 0.12
|
339
|
+
SLOW_THRESHOLD = 10 # seconds
|
340
|
+
|
341
|
+
def __init__(self):
|
342
|
+
self._stop_event = threading.Event()
|
343
|
+
self._thread = None
|
344
|
+
self._start_time = None
|
345
|
+
self.console = Console()
|
346
|
+
self._last_frame = None
|
347
|
+
self._last_slow = False
|
348
|
+
|
349
|
+
def start(self):
|
350
|
+
self._stop_event.clear()
|
351
|
+
self._start_time = time.time()
|
352
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
353
|
+
self._thread.start()
|
354
|
+
|
355
|
+
def _spin(self):
|
356
|
+
idx = 0
|
357
|
+
while not self._stop_event.is_set():
|
358
|
+
elapsed = time.time() - self._start_time
|
359
|
+
if elapsed > self.SLOW_THRESHOLD:
|
360
|
+
txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
|
361
|
+
self._last_frame = self.SLOW_FRAME
|
362
|
+
self._last_slow = True
|
363
|
+
else:
|
364
|
+
frame = self.FRAMES[idx % len(self.FRAMES)]
|
365
|
+
txt = Text(frame, style=Style(color="cyan", bold=True))
|
366
|
+
self._last_frame = frame
|
367
|
+
self._last_slow = False
|
368
|
+
self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
|
369
|
+
time.sleep(self.INTERVAL)
|
370
|
+
idx += 1
|
371
|
+
self.console.print(" " * 40, end="\r") # Clear line
|
372
|
+
|
373
|
+
def stop(self, final_message="Done!"):
|
374
|
+
self._stop_event.set()
|
375
|
+
if self._thread:
|
376
|
+
self._thread.join()
|
377
|
+
self.console.print(Text(final_message, style=Style(color="green", bold=True)))
|
378
|
+
|
379
|
+
def current_spinner_state(self):
|
380
|
+
if self._last_slow:
|
381
|
+
return self.SLOW_FRAME
|
382
|
+
return self._last_frame or self.FRAMES[0]
|
383
|
+
|
384
|
+
|
385
|
+
def print_operation_box(op_type, results, params=None, result_type="mission", taking_long=False):
|
386
|
+
emoji = "🕵️" if result_type == "mission" else "🔍"
|
387
|
+
style = 'success' if result_type == "mission" else 'default'
|
388
|
+
box_title = op_type if op_type else ("MissionImprobable Output" if result_type == "mission" else "Results")
|
389
|
+
summary_lines = []
|
390
|
+
count = len(results) if isinstance(results, list) else 0
|
391
|
+
summary_lines.append(f"Results: {count}")
|
392
|
+
if params:
|
393
|
+
for k, v in params.items():
|
394
|
+
summary_lines.append(f"{k.capitalize()}: {v}")
|
395
|
+
box_content = "\n".join(summary_lines + ["\n".join(map(str, results))])
|
396
|
+
ansi_box(box_title, box_content, count=count, params=params, style=style if not taking_long else 'warning', emoji=emoji)
|
278
397
|
|
279
398
|
# Standard Python entry point
|
280
399
|
if __name__ == "__main__":
|
@@ -286,6 +405,19 @@ if __name__ == "__main__":
|
|
286
405
|
]
|
287
406
|
blueprint = MissionImprobableBlueprint(blueprint_id="demo-1")
|
288
407
|
async def run_and_print():
|
289
|
-
|
290
|
-
|
408
|
+
spinner = MissionImprobableSpinner()
|
409
|
+
spinner.start()
|
410
|
+
try:
|
411
|
+
all_results = []
|
412
|
+
async for response in blueprint.run(messages):
|
413
|
+
content = response["messages"][0]["content"]
|
414
|
+
all_results.append(content)
|
415
|
+
finally:
|
416
|
+
spinner.stop()
|
417
|
+
print_operation_box(
|
418
|
+
op_type="MissionImprobable Output",
|
419
|
+
results=all_results,
|
420
|
+
params={"prompt": messages[0]["content"]},
|
421
|
+
result_type="mission"
|
422
|
+
)
|
291
423
|
asyncio.run(run_and_print())
|
@@ -17,6 +17,8 @@ import subprocess
|
|
17
17
|
import sys
|
18
18
|
import shlex # Import shlex
|
19
19
|
from typing import Dict, Any, List, ClassVar, Optional
|
20
|
+
import time
|
21
|
+
from swarm.core.blueprint_ux import BlueprintUXImproved
|
20
22
|
|
21
23
|
# Ensure src is in path for BlueprintBase import
|
22
24
|
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
@@ -40,13 +42,14 @@ logger = logging.getLogger(__name__)
|
|
40
42
|
# --- Cloud CLI Function Tools ---
|
41
43
|
@function_tool
|
42
44
|
def aws_cli(command: str) -> str:
|
43
|
-
"""Executes an AWS CLI command (e.g., 's3 ls', 'ec2 describe-instances'). Assumes pre-authentication."""
|
45
|
+
"""Executes an AWS CLI command (e.g., 's3 ls', 'ec2 describe-instances'). Assumes pre-authentication. Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 120s)."""
|
44
46
|
if not command: return "Error: No AWS command provided."
|
45
47
|
try:
|
46
|
-
|
48
|
+
import os
|
49
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "120"))
|
47
50
|
cmd_parts = ["aws"] + shlex.split(command)
|
48
51
|
logger.info(f"Executing AWS CLI: {' '.join(cmd_parts)}")
|
49
|
-
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=
|
52
|
+
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=timeout)
|
50
53
|
output = result.stdout.strip()
|
51
54
|
logger.debug(f"AWS CLI success. Output:\n{output[:500]}...")
|
52
55
|
return f"OK: AWS command successful.\nOutput:\n{output}"
|
@@ -59,19 +62,21 @@ def aws_cli(command: str) -> str:
|
|
59
62
|
return f"Error executing AWS command '{command}': {error_output}"
|
60
63
|
except subprocess.TimeoutExpired:
|
61
64
|
logger.error(f"AWS CLI command '{command}' timed out.")
|
62
|
-
return f"Error: AWS CLI command '{command}' timed out."
|
65
|
+
return f"Error: AWS CLI command '{command}' timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '120')} seconds."
|
63
66
|
except Exception as e:
|
64
67
|
logger.error(f"Unexpected error during AWS CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
|
65
68
|
return f"Error: Unexpected error during AWS CLI: {e}"
|
66
69
|
|
67
70
|
@function_tool
|
68
71
|
def fly_cli(command: str) -> str:
|
69
|
-
"""Executes a Fly.io CLI command ('flyctl ...'). Assumes pre-authentication ('flyctl auth login')."""
|
72
|
+
"""Executes a Fly.io CLI command ('flyctl ...'). Assumes pre-authentication ('flyctl auth login'). Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 120s)."""
|
70
73
|
if not command: return "Error: No Fly command provided."
|
71
74
|
try:
|
75
|
+
import os
|
76
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "120"))
|
72
77
|
cmd_parts = ["flyctl"] + shlex.split(command)
|
73
78
|
logger.info(f"Executing Fly CLI: {' '.join(cmd_parts)}")
|
74
|
-
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=
|
79
|
+
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=timeout)
|
75
80
|
output = result.stdout.strip()
|
76
81
|
logger.debug(f"Fly CLI success. Output:\n{output[:500]}...")
|
77
82
|
return f"OK: Fly command successful.\nOutput:\n{output}"
|
@@ -84,19 +89,21 @@ def fly_cli(command: str) -> str:
|
|
84
89
|
return f"Error executing Fly command '{command}': {error_output}"
|
85
90
|
except subprocess.TimeoutExpired:
|
86
91
|
logger.error(f"Fly CLI command '{command}' timed out.")
|
87
|
-
return f"Error: Fly CLI command '{command}' timed out."
|
92
|
+
return f"Error: Fly CLI command '{command}' timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '120')} seconds."
|
88
93
|
except Exception as e:
|
89
94
|
logger.error(f"Unexpected error during Fly CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
|
90
95
|
return f"Error: Unexpected error during Fly CLI: {e}"
|
91
96
|
|
92
97
|
@function_tool
|
93
98
|
def vercel_cli(command: str) -> str:
|
94
|
-
"""Executes a Vercel CLI command ('vercel ...'). Assumes pre-authentication ('vercel login')."""
|
99
|
+
"""Executes a Vercel CLI command ('vercel ...'). Assumes pre-authentication ('vercel login'). Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 120s)."""
|
95
100
|
if not command: return "Error: No Vercel command provided."
|
96
101
|
try:
|
102
|
+
import os
|
103
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "120"))
|
97
104
|
cmd_parts = ["vercel"] + shlex.split(command)
|
98
105
|
logger.info(f"Executing Vercel CLI: {' '.join(cmd_parts)}")
|
99
|
-
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=
|
106
|
+
result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=timeout)
|
100
107
|
output = result.stdout.strip()
|
101
108
|
logger.debug(f"Vercel CLI success. Output:\n{output[:500]}...")
|
102
109
|
return f"OK: Vercel command successful.\nOutput:\n{output}"
|
@@ -109,7 +116,7 @@ def vercel_cli(command: str) -> str:
|
|
109
116
|
return f"Error executing Vercel command '{command}': {error_output}"
|
110
117
|
except subprocess.TimeoutExpired:
|
111
118
|
logger.error(f"Vercel CLI command '{command}' timed out.")
|
112
|
-
return f"Error: Vercel CLI command '{command}' timed out."
|
119
|
+
return f"Error: Vercel CLI command '{command}' timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '120')} seconds."
|
113
120
|
except Exception as e:
|
114
121
|
logger.error(f"Unexpected error during Vercel CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
|
115
122
|
return f"Error: Unexpected error during Vercel CLI: {e}"
|
@@ -139,21 +146,21 @@ class MonkaiMagicBlueprint(BlueprintBase):
|
|
139
146
|
"env_vars": ["AWS_REGION", "FLY_REGION", "VERCEL_ORG_ID"] # Optional vars for instruction hints
|
140
147
|
}
|
141
148
|
|
149
|
+
def __init__(self, blueprint_id: str = "monkai_magic", config=None, config_path=None, **kwargs):
|
150
|
+
super().__init__(blueprint_id=blueprint_id, config=config, config_path=config_path, **kwargs)
|
151
|
+
self.blueprint_id = blueprint_id
|
152
|
+
self.config_path = config_path
|
153
|
+
self._config = config if config is not None else None
|
154
|
+
self._llm_profile_name = None
|
155
|
+
self._llm_profile_data = None
|
156
|
+
self._markdown_output = None
|
157
|
+
# Add other attributes as needed for MonkaiMagic
|
158
|
+
# ...
|
159
|
+
|
142
160
|
# Caches
|
143
161
|
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
144
162
|
_model_instance_cache: Dict[str, Model] = {}
|
145
163
|
|
146
|
-
def __init__(self, *args, **kwargs):
|
147
|
-
super().__init__(*args, **kwargs)
|
148
|
-
class DummyLLM:
|
149
|
-
def chat_completion_stream(self, messages, **_):
|
150
|
-
class DummyStream:
|
151
|
-
def __aiter__(self): return self
|
152
|
-
async def __anext__(self):
|
153
|
-
raise StopAsyncIteration
|
154
|
-
return DummyStream()
|
155
|
-
self.llm = DummyLLM()
|
156
|
-
|
157
164
|
# --- Model Instantiation Helper --- (Standard helper)
|
158
165
|
def _get_model_instance(self, profile_name: str) -> Model:
|
159
166
|
"""Retrieves or creates an LLM Model instance."""
|
@@ -187,26 +194,57 @@ class MonkaiMagicBlueprint(BlueprintBase):
|
|
187
194
|
def render_prompt(self, template_name: str, context: dict) -> str:
|
188
195
|
return f"User request: {context.get('user_request', '')}\nHistory: {context.get('history', '')}\nAvailable tools: {', '.join(context.get('available_tools', []))}"
|
189
196
|
|
190
|
-
async def run(self, messages: list)
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
197
|
+
async def run(self, messages: list, **kwargs):
|
198
|
+
"""Main execution entry point for the MonkaiMagic blueprint."""
|
199
|
+
logger.info("MonkaiMagicBlueprint run method called.")
|
200
|
+
instruction = messages[-1].get("content", "") if messages else ""
|
201
|
+
from agents import Runner
|
202
|
+
ux = BlueprintUXImproved(style="serious")
|
203
|
+
spinner_idx = 0
|
204
|
+
start_time = time.time()
|
205
|
+
spinner_yield_interval = 1.0 # seconds
|
206
|
+
last_spinner_time = start_time
|
207
|
+
yielded_spinner = False
|
208
|
+
result_chunks = []
|
209
|
+
try:
|
210
|
+
runner_gen = Runner.run(self.create_starting_agent([]), instruction)
|
211
|
+
while True:
|
212
|
+
now = time.time()
|
213
|
+
try:
|
214
|
+
chunk = next(runner_gen)
|
215
|
+
result_chunks.append(chunk)
|
216
|
+
# If chunk is a final result, wrap and yield
|
217
|
+
if chunk and isinstance(chunk, dict) and "messages" in chunk:
|
218
|
+
content = chunk["messages"][0]["content"] if chunk["messages"] else ""
|
219
|
+
summary = ux.summary("Operation", len(result_chunks), {"instruction": instruction[:40]})
|
220
|
+
box = ux.ansi_emoji_box(
|
221
|
+
title="MonkaiMagic Result",
|
222
|
+
content=content,
|
223
|
+
summary=summary,
|
224
|
+
params={"instruction": instruction[:40]},
|
225
|
+
result_count=len(result_chunks),
|
226
|
+
op_type="run",
|
227
|
+
status="success"
|
228
|
+
)
|
229
|
+
yield {"messages": [{"role": "assistant", "content": box}]}
|
230
|
+
else:
|
231
|
+
yield chunk
|
232
|
+
yielded_spinner = False
|
233
|
+
except StopIteration:
|
234
|
+
break
|
235
|
+
except Exception:
|
236
|
+
if now - last_spinner_time >= spinner_yield_interval:
|
237
|
+
taking_long = (now - start_time > 10)
|
238
|
+
spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
|
239
|
+
yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
|
240
|
+
spinner_idx += 1
|
241
|
+
last_spinner_time = now
|
242
|
+
yielded_spinner = True
|
243
|
+
if not result_chunks and not yielded_spinner:
|
244
|
+
yield {"messages": [{"role": "assistant", "content": ux.spinner(0)}]}
|
245
|
+
except Exception as e:
|
246
|
+
logger.error(f"Error during MonkaiMagic run: {e}", exc_info=True)
|
247
|
+
yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
|
210
248
|
|
211
249
|
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
212
250
|
"""Creates the MonkaiMagic agent team and returns Tripitaka."""
|