open-swarm 0.1.1745125933__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.
Files changed (52) hide show
  1. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/METADATA +12 -8
  2. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/RECORD +52 -25
  3. swarm/blueprints/README.md +19 -18
  4. swarm/blueprints/blueprint_audit_status.json +1 -1
  5. swarm/blueprints/chatbot/blueprint_chatbot.py +160 -72
  6. swarm/blueprints/codey/README.md +88 -8
  7. swarm/blueprints/codey/blueprint_codey.py +1116 -210
  8. swarm/blueprints/codey/codey_cli.py +10 -0
  9. swarm/blueprints/codey/session_logs/session_2025-04-19T01-15-31.md +17 -0
  10. swarm/blueprints/codey/session_logs/session_2025-04-19T01-16-03.md +17 -0
  11. swarm/blueprints/common/operation_box_utils.py +83 -0
  12. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +21 -298
  13. swarm/blueprints/divine_code/blueprint_divine_code.py +182 -9
  14. swarm/blueprints/django_chat/blueprint_django_chat.py +150 -24
  15. swarm/blueprints/echocraft/blueprint_echocraft.py +142 -13
  16. swarm/blueprints/geese/README.md +97 -0
  17. swarm/blueprints/geese/blueprint_geese.py +677 -93
  18. swarm/blueprints/geese/geese_cli.py +102 -0
  19. swarm/blueprints/jeeves/blueprint_jeeves.py +712 -0
  20. swarm/blueprints/jeeves/jeeves_cli.py +55 -0
  21. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +109 -22
  22. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +172 -40
  23. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +79 -41
  24. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +82 -35
  25. swarm/blueprints/omniplex/blueprint_omniplex.py +56 -24
  26. swarm/blueprints/poets/blueprint_poets.py +141 -100
  27. swarm/blueprints/poets/poets_cli.py +23 -0
  28. swarm/blueprints/rue_code/README.md +8 -0
  29. swarm/blueprints/rue_code/blueprint_rue_code.py +188 -20
  30. swarm/blueprints/rue_code/rue_code_cli.py +43 -0
  31. swarm/blueprints/stewie/apps.py +12 -0
  32. swarm/blueprints/stewie/blueprint_family_ties.py +349 -0
  33. swarm/blueprints/stewie/models.py +19 -0
  34. swarm/blueprints/stewie/serializers.py +10 -0
  35. swarm/blueprints/stewie/settings.py +17 -0
  36. swarm/blueprints/stewie/urls.py +11 -0
  37. swarm/blueprints/stewie/views.py +26 -0
  38. swarm/blueprints/suggestion/blueprint_suggestion.py +54 -39
  39. swarm/blueprints/whinge_surf/README.md +22 -0
  40. swarm/blueprints/whinge_surf/__init__.py +1 -0
  41. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +565 -0
  42. swarm/blueprints/whinge_surf/whinge_surf_cli.py +99 -0
  43. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +66 -37
  44. swarm/blueprints/zeus/__init__.py +2 -0
  45. swarm/blueprints/zeus/apps.py +4 -0
  46. swarm/blueprints/zeus/blueprint_zeus.py +270 -0
  47. swarm/blueprints/zeus/zeus_cli.py +13 -0
  48. swarm/cli/async_input.py +65 -0
  49. swarm/cli/async_input_demo.py +32 -0
  50. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/WHEEL +0 -0
  51. {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/entry_points.txt +0 -0
  52. {open_swarm-0.1.1745125933.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
- Execute one or more shell commands.
152
- Supports commands separated by '&&' or newlines for sequential execution.
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
- import subprocess
154
+ logger.info(f"Executing shell command: {command}")
156
155
  try:
157
- # Split multiple commands
158
- if '&&' in command:
159
- cmds = [c.strip() for c in command.split('&&')]
160
- elif '\n' in command:
161
- cmds = [c.strip() for c in command.splitlines() if c.strip()]
162
- else:
163
- cmds = [command]
164
- outputs: Dict[str, str] = {}
165
- for cmd in cmds:
166
- try:
167
- result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
168
- outputs[cmd] = result.stdout + result.stderr
169
- except Exception as e:
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
- return f"ERROR: {e}"
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
- async for response in blueprint.run(messages):
385
- print(json.dumps(response, indent=2))
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
- import subprocess
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
- result = subprocess.run(command, shell=True, capture_output=True, text=True)
66
- return result.stdout + result.stderr
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
- return f"ERROR: {e}"
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: Optional[Path] = None, **kwargs):
104
- if blueprint_id is None:
105
- blueprint_id = "mission-improbable"
106
- super().__init__(blueprint_id, config_path=config_path, **kwargs)
107
- class DummyLLM:
108
- def chat_completion_stream(self, messages, **_):
109
- class DummyStream:
110
- def __aiter__(self): return self
111
- async def __anext__(self):
112
- raise StopAsyncIteration
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) -> object:
256
- last_user_message = next((m['content'] for m in reversed(messages) if m['role'] == 'user'), None)
257
- if not last_user_message:
258
- yield {"messages": [{"role": "assistant", "content": "I need a user message to proceed."}]}
259
- return
260
- prompt_context = {
261
- "user_request": last_user_message,
262
- "history": messages[:-1],
263
- "available_tools": ["mission_improbable"]
264
- }
265
- rendered_prompt = self.render_prompt("mission_improbable_prompt.j2", prompt_context)
266
- yield {
267
- "messages": [
268
- {
269
- "role": "assistant",
270
- "content": f"[MissionImprobable LLM] Would respond to: {rendered_prompt}"
271
- }
272
- ]
273
- }
274
- return
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
- def render_prompt(self, template_name: str, context: dict) -> str:
277
- return f"User request: {context.get('user_request', '')}\nHistory: {context.get('history', '')}\nAvailable tools: {', '.join(context.get('available_tools', []))}"
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
- async for response in blueprint.run(messages):
290
- print(json.dumps(response, indent=2))
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
- # Avoid shell=True if possible, split command carefully
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=120)
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=120)
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=120)
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) -> object:
191
- last_user_message = next((m['content'] for m in reversed(messages) if m['role'] == 'user'), None)
192
- if not last_user_message:
193
- yield {"messages": [{"role": "assistant", "content": "I need a user message to proceed."}]}
194
- return
195
- prompt_context = {
196
- "user_request": last_user_message,
197
- "history": messages[:-1],
198
- "available_tools": ["monkai_magic"]
199
- }
200
- rendered_prompt = self.render_prompt("monkai_magic_prompt.j2", prompt_context)
201
- yield {
202
- "messages": [
203
- {
204
- "role": "assistant",
205
- "content": f"[MonkaiMagic LLM] Would respond to: {rendered_prompt}"
206
- }
207
- ]
208
- }
209
- return
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."""