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.
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/METADATA +12 -8
- {open_swarm-0.1.1745125933.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.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1745125933.dist-info → open_swarm-0.1.1745126277.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,7 @@ import logging
|
|
8
8
|
import os
|
9
9
|
import sys
|
10
10
|
from typing import Dict, Any, List, ClassVar, Optional
|
11
|
+
from swarm.blueprints.common.operation_box_utils import display_operation_box
|
11
12
|
|
12
13
|
# Ensure src is in path for BlueprintBase import
|
13
14
|
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
@@ -24,6 +25,10 @@ try:
|
|
24
25
|
from openai import AsyncOpenAI
|
25
26
|
from swarm.core.blueprint_base import BlueprintBase
|
26
27
|
from swarm.core.blueprint_ux import BlueprintUX
|
28
|
+
from rich.console import Console
|
29
|
+
from rich.text import Text
|
30
|
+
from rich.style import Style
|
31
|
+
import threading, time
|
27
32
|
except ImportError as e:
|
28
33
|
print(f"ERROR: Import failed in DivineOpsBlueprint: {e}. Check 'openai-agents' install and project structure.")
|
29
34
|
print(f"sys.path: {sys.path}")
|
@@ -121,8 +126,84 @@ Available MCP Tools (if provided): sequential-thinking, filesystem.
|
|
121
126
|
You also have fileops capabilities: read_file, write_file, list_files, execute_shell_command.
|
122
127
|
"""
|
123
128
|
|
124
|
-
# Spinner
|
125
|
-
|
129
|
+
# --- Spinner and ANSI/emoji operation box for unified UX ---
|
130
|
+
from swarm.ux.ansi_box import ansi_box
|
131
|
+
from rich.console import Console
|
132
|
+
from rich.style import Style
|
133
|
+
from rich.text import Text
|
134
|
+
|
135
|
+
# Patch Spinner to match Open Swarm UX (FRAMES, slow warning, etc.)
|
136
|
+
class DivineOpsSpinner:
|
137
|
+
FRAMES = [
|
138
|
+
"Generating.", "Generating..", "Generating...", "Running...",
|
139
|
+
"⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
|
140
|
+
"⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
|
141
|
+
"⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
|
142
|
+
]
|
143
|
+
SLOW_FRAME = "Generating... Taking longer than expected"
|
144
|
+
INTERVAL = 0.12
|
145
|
+
SLOW_THRESHOLD = 10 # seconds
|
146
|
+
|
147
|
+
def __init__(self):
|
148
|
+
import threading, time
|
149
|
+
self._stop_event = threading.Event()
|
150
|
+
self._thread = None
|
151
|
+
self._start_time = None
|
152
|
+
self.console = Console()
|
153
|
+
self._last_frame = None
|
154
|
+
self._last_slow = False
|
155
|
+
|
156
|
+
def start(self):
|
157
|
+
import time
|
158
|
+
self._stop_event.clear()
|
159
|
+
self._start_time = time.time()
|
160
|
+
import threading
|
161
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
162
|
+
self._thread.start()
|
163
|
+
|
164
|
+
def _spin(self):
|
165
|
+
import time
|
166
|
+
idx = 0
|
167
|
+
while not self._stop_event.is_set():
|
168
|
+
elapsed = time.time() - self._start_time
|
169
|
+
if elapsed > self.SLOW_THRESHOLD:
|
170
|
+
txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
|
171
|
+
self._last_frame = self.SLOW_FRAME
|
172
|
+
self._last_slow = True
|
173
|
+
else:
|
174
|
+
frame = self.FRAMES[idx % len(self.FRAMES)]
|
175
|
+
txt = Text(frame, style=Style(color="cyan", bold=True))
|
176
|
+
self._last_frame = frame
|
177
|
+
self._last_slow = False
|
178
|
+
self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
|
179
|
+
time.sleep(self.INTERVAL)
|
180
|
+
idx += 1
|
181
|
+
self.console.print(" " * 40, end="\r") # Clear line
|
182
|
+
|
183
|
+
def stop(self, final_message="Done!"):
|
184
|
+
self._stop_event.set()
|
185
|
+
if self._thread:
|
186
|
+
self._thread.join()
|
187
|
+
self.console.print(Text(final_message, style=Style(color="green", bold=True)))
|
188
|
+
|
189
|
+
def current_spinner_state(self):
|
190
|
+
if self._last_slow:
|
191
|
+
return self.SLOW_FRAME
|
192
|
+
return self._last_frame or self.FRAMES[0]
|
193
|
+
|
194
|
+
|
195
|
+
def print_operation_box(op_type, results, params=None, result_type="divine", taking_long=False):
|
196
|
+
emoji = "⚡" if result_type == "divine" else "🔍"
|
197
|
+
style = 'success' if result_type == "divine" else 'default'
|
198
|
+
box_title = op_type if op_type else ("Divine Output" if result_type == "divine" else "Results")
|
199
|
+
summary_lines = []
|
200
|
+
count = len(results) if isinstance(results, list) else 0
|
201
|
+
summary_lines.append(f"Results: {count}")
|
202
|
+
if params:
|
203
|
+
for k, v in params.items():
|
204
|
+
summary_lines.append(f"{k.capitalize()}: {v}")
|
205
|
+
box_content = "\n".join(summary_lines + ["\n".join(map(str, results))])
|
206
|
+
ansi_box(box_title, box_content, count=count, params=params, style=style if not taking_long else 'warning', emoji=emoji)
|
126
207
|
|
127
208
|
# --- FileOps Tool Logic Definitions ---
|
128
209
|
# Patch: Expose underlying fileops functions for direct testing
|
@@ -149,12 +230,28 @@ def list_files(directory: str = '.') -> str:
|
|
149
230
|
except Exception as e:
|
150
231
|
return f"ERROR: {e}"
|
151
232
|
def execute_shell_command(command: str) -> str:
|
152
|
-
|
233
|
+
"""
|
234
|
+
Executes a shell command and returns its stdout and stderr.
|
235
|
+
Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
|
236
|
+
"""
|
237
|
+
logger.info(f"Executing shell command: {command}")
|
153
238
|
try:
|
154
|
-
|
155
|
-
|
239
|
+
import os
|
240
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
|
241
|
+
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
|
242
|
+
output = f"Exit Code: {result.returncode}\n"
|
243
|
+
if result.stdout:
|
244
|
+
output += f"STDOUT:\n{result.stdout}\n"
|
245
|
+
if result.stderr:
|
246
|
+
output += f"STDERR:\n{result.stderr}\n"
|
247
|
+
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
248
|
+
return output.strip()
|
249
|
+
except subprocess.TimeoutExpired:
|
250
|
+
logger.error(f"Command timed out: {command}")
|
251
|
+
return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
|
156
252
|
except Exception as e:
|
157
|
-
|
253
|
+
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
254
|
+
return f"Error executing command: {e}"
|
158
255
|
read_file_tool = PatchedFunctionTool(read_file, 'read_file')
|
159
256
|
write_file_tool = PatchedFunctionTool(write_file, 'write_file')
|
160
257
|
list_files_tool = PatchedFunctionTool(list_files, 'list_files')
|
@@ -166,6 +263,36 @@ class DivineOpsBlueprint(BlueprintBase):
|
|
166
263
|
super().__init__(blueprint_id, config_path=config_path, **kwargs)
|
167
264
|
# Use serious style for DivineOps
|
168
265
|
self.ux = BlueprintUX(style="serious")
|
266
|
+
# Spinner for pantheon operations
|
267
|
+
self._spinner = None
|
268
|
+
|
269
|
+
class Spinner:
|
270
|
+
FRAMES = [
|
271
|
+
"⚡ Summoning Pantheon...",
|
272
|
+
"🔥 Forging Tools...",
|
273
|
+
"🌩️ Commanding Zeus...",
|
274
|
+
"✨ Awakening Divinity..."
|
275
|
+
]
|
276
|
+
INTERVAL = 0.15
|
277
|
+
def __init__(self):
|
278
|
+
self._stop = threading.Event()
|
279
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
280
|
+
self._idx = 0
|
281
|
+
self.console = Console()
|
282
|
+
def start(self):
|
283
|
+
self._stop.clear()
|
284
|
+
self._thread.start()
|
285
|
+
def _spin(self):
|
286
|
+
while not self._stop.is_set():
|
287
|
+
frame = DivineOpsBlueprint.Spinner.FRAMES[self._idx % len(DivineOpsBlueprint.Spinner.FRAMES)]
|
288
|
+
self.console.print(Text(frame, style=Style(color="magenta", bold=True)), end="\r")
|
289
|
+
self._idx += 1
|
290
|
+
time.sleep(DivineOpsBlueprint.Spinner.INTERVAL)
|
291
|
+
self.console.print(" " * 40, end="\r")
|
292
|
+
def stop(self, final="🌟 All pantheon tasks complete!"):
|
293
|
+
self._stop.set()
|
294
|
+
self._thread.join()
|
295
|
+
self.console.print(Text(final, style=Style(color="green", bold=True)))
|
169
296
|
|
170
297
|
""" Divine Ops: Streamlined Software Dev & Sysadmin Team Blueprint using openai-agents """
|
171
298
|
metadata: ClassVar[Dict[str, Any]] = {
|
@@ -282,11 +409,16 @@ class DivineOpsBlueprint(BlueprintBase):
|
|
282
409
|
return zeus_agent
|
283
410
|
|
284
411
|
async def run(self, messages: List[Dict[str, Any]], **kwargs) -> Any:
|
285
|
-
"""Main execution entry point for the DivineOps blueprint."""
|
412
|
+
"""Main execution entry point for the DivineOps blueprint with ANSI spinner."""
|
286
413
|
logger.info("DivineOpsBlueprint run method called.")
|
414
|
+
# Start spinner
|
415
|
+
self._spinner = DivineOpsSpinner()
|
416
|
+
self._spinner.start()
|
287
417
|
instruction = messages[-1].get("content", "") if messages else ""
|
288
418
|
async for chunk in self._run_non_interactive(instruction, **kwargs):
|
289
419
|
yield chunk
|
420
|
+
# Stop spinner and show completion
|
421
|
+
self._spinner.stop()
|
290
422
|
logger.info("DivineOpsBlueprint run method finished.")
|
291
423
|
|
292
424
|
async def _run_non_interactive(self, instruction: str, **kwargs) -> Any:
|
@@ -302,6 +434,18 @@ class DivineOpsBlueprint(BlueprintBase):
|
|
302
434
|
logger.error(f"Error during non-interactive run: {e}", exc_info=True)
|
303
435
|
yield { "messages": [ {"role": "assistant", "content": f"An error occurred: {e}"} ] }
|
304
436
|
|
437
|
+
class ZeusBlueprint(BlueprintBase):
|
438
|
+
def __init__(self, blueprint_id: str = "zeus", config=None, config_path=None, **kwargs):
|
439
|
+
super().__init__()
|
440
|
+
self.blueprint_id = blueprint_id
|
441
|
+
self.config_path = config_path
|
442
|
+
self._config = config if config is not None else None
|
443
|
+
self._llm_profile_name = None
|
444
|
+
self._llm_profile_data = None
|
445
|
+
self._markdown_output = None
|
446
|
+
# Add other attributes as needed for Zeus
|
447
|
+
# ...
|
448
|
+
|
305
449
|
# Standard Python entry point
|
306
450
|
if __name__ == "__main__":
|
307
451
|
import asyncio
|
@@ -312,6 +456,35 @@ if __name__ == "__main__":
|
|
312
456
|
]
|
313
457
|
blueprint = DivineOpsBlueprint(blueprint_id="demo-1")
|
314
458
|
async def run_and_print():
|
315
|
-
|
316
|
-
|
459
|
+
spinner = DivineOpsSpinner()
|
460
|
+
spinner.start()
|
461
|
+
try:
|
462
|
+
all_results = []
|
463
|
+
async for response in blueprint.run(messages):
|
464
|
+
content = response["messages"][0]["content"] if (isinstance(response, dict) and "messages" in response and response["messages"]) else str(response)
|
465
|
+
all_results.append(content)
|
466
|
+
# Enhanced progressive output
|
467
|
+
if isinstance(response, dict) and (response.get("progress") or response.get("matches")):
|
468
|
+
display_operation_box(
|
469
|
+
title="Progressive Operation",
|
470
|
+
content="\n".join(response.get("matches", [])),
|
471
|
+
style="bold cyan" if response.get("type") == "code_search" else "bold magenta",
|
472
|
+
result_count=len(response.get("matches", [])) if response.get("matches") is not None else None,
|
473
|
+
params={k: v for k, v in response.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
474
|
+
progress_line=response.get('progress'),
|
475
|
+
total_lines=response.get('total'),
|
476
|
+
spinner_state=spinner.current_spinner_state() if hasattr(spinner, 'current_spinner_state') else None,
|
477
|
+
op_type=response.get("type", "search"),
|
478
|
+
emoji="🔍" if response.get("type") == "code_search" else "🧠"
|
479
|
+
)
|
480
|
+
finally:
|
481
|
+
spinner.stop()
|
482
|
+
display_operation_box(
|
483
|
+
title="Divine Output",
|
484
|
+
content="\n".join(all_results),
|
485
|
+
style="bold green",
|
486
|
+
result_count=len(all_results),
|
487
|
+
params={"prompt": messages[0]["content"]},
|
488
|
+
op_type="divine"
|
489
|
+
)
|
317
490
|
asyncio.run(run_and_print())
|
@@ -9,6 +9,9 @@ import logging
|
|
9
9
|
import sys
|
10
10
|
import os
|
11
11
|
from typing import Dict, Any, List
|
12
|
+
from swarm.blueprints.common.operation_box_utils import display_operation_box
|
13
|
+
from swarm.core.blueprint_ux import BlueprintUXImproved
|
14
|
+
import time
|
12
15
|
|
13
16
|
# --- Logging Setup ---
|
14
17
|
def setup_logging():
|
@@ -49,9 +52,78 @@ from swarm.utils.logger_setup import setup_logger
|
|
49
52
|
|
50
53
|
logger = setup_logger(__name__)
|
51
54
|
|
55
|
+
# --- Spinner and ANSI/emoji operation box for unified UX (for CLI/dev runs) ---
|
56
|
+
from swarm.ux.ansi_box import ansi_box
|
57
|
+
from rich.console import Console
|
58
|
+
from rich.style import Style
|
59
|
+
from rich.text import Text
|
60
|
+
import threading
|
61
|
+
import time
|
62
|
+
|
63
|
+
class DjangoChatSpinner:
|
64
|
+
FRAMES = [
|
65
|
+
"Generating.", "Generating..", "Generating...", "Running...",
|
66
|
+
"⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
|
67
|
+
"⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
|
68
|
+
"⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
|
69
|
+
]
|
70
|
+
SLOW_FRAME = "Generating... Taking longer than expected"
|
71
|
+
INTERVAL = 0.12
|
72
|
+
SLOW_THRESHOLD = 10 # seconds
|
73
|
+
|
74
|
+
def __init__(self):
|
75
|
+
self._stop_event = threading.Event()
|
76
|
+
self._thread = None
|
77
|
+
self._start_time = None
|
78
|
+
self.console = Console()
|
79
|
+
self._last_frame = None
|
80
|
+
self._last_slow = False
|
81
|
+
|
82
|
+
def start(self):
|
83
|
+
self._stop_event.clear()
|
84
|
+
self._start_time = time.time()
|
85
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
86
|
+
self._thread.start()
|
87
|
+
|
88
|
+
def _spin(self):
|
89
|
+
idx = 0
|
90
|
+
while not self._stop_event.is_set():
|
91
|
+
elapsed = time.time() - self._start_time
|
92
|
+
if elapsed > self.SLOW_THRESHOLD:
|
93
|
+
txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
|
94
|
+
self._last_frame = self.SLOW_FRAME
|
95
|
+
self._last_slow = True
|
96
|
+
else:
|
97
|
+
frame = self.FRAMES[idx % len(self.FRAMES)]
|
98
|
+
txt = Text(frame, style=Style(color="cyan", bold=True))
|
99
|
+
self._last_frame = frame
|
100
|
+
self._last_slow = False
|
101
|
+
self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
|
102
|
+
time.sleep(self.INTERVAL)
|
103
|
+
idx += 1
|
104
|
+
self.console.print(" " * 40, end="\r") # Clear line
|
105
|
+
|
106
|
+
def stop(self, final_message="Done!"):
|
107
|
+
self._stop_event.set()
|
108
|
+
if self._thread:
|
109
|
+
self._thread.join()
|
110
|
+
self.console.print(Text(final_message, style=Style(color="green", bold=True)))
|
111
|
+
|
112
|
+
def current_spinner_state(self):
|
113
|
+
if self._last_slow:
|
114
|
+
return self.SLOW_FRAME
|
115
|
+
return self._last_frame or self.FRAMES[0]
|
116
|
+
|
117
|
+
|
52
118
|
class DjangoChatBlueprint(Blueprint):
|
53
|
-
def __init__(self,
|
54
|
-
super().__init__(
|
119
|
+
def __init__(self, blueprint_id: str = "django_chat", config=None, config_path=None, **kwargs):
|
120
|
+
super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
|
121
|
+
self.blueprint_id = blueprint_id
|
122
|
+
self.config_path = config_path
|
123
|
+
self._config = config if config is not None else None
|
124
|
+
self._llm_profile_name = None
|
125
|
+
self._llm_profile_data = None
|
126
|
+
self._markdown_output = None
|
55
127
|
class DummyLLM:
|
56
128
|
def chat_completion_stream(self, messages, **_):
|
57
129
|
class DummyStream:
|
@@ -100,26 +172,51 @@ class DjangoChatBlueprint(Blueprint):
|
|
100
172
|
def render_prompt(self, template_name: str, context: dict) -> str:
|
101
173
|
return f"User request: {context.get('user_request', '')}\nHistory: {context.get('history', '')}\nAvailable tools: {', '.join(context.get('available_tools', []))}"
|
102
174
|
|
103
|
-
async def run(self, messages: List[Dict[str, str]])
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
175
|
+
async def run(self, messages: List[Dict[str, str]]):
|
176
|
+
"""Main execution entry point for the DjangoChat blueprint."""
|
177
|
+
logger.info("DjangoChatBlueprint run method called.")
|
178
|
+
instruction = messages[-1].get("content", "") if messages else ""
|
179
|
+
ux = BlueprintUXImproved(style="serious")
|
180
|
+
spinner_idx = 0
|
181
|
+
start_time = time.time()
|
182
|
+
spinner_yield_interval = 1.0 # seconds
|
183
|
+
last_spinner_time = start_time
|
184
|
+
yielded_spinner = False
|
185
|
+
result_chunks = []
|
186
|
+
try:
|
187
|
+
# Simulate agent runner pattern (replace with actual agent logic if available)
|
188
|
+
prompt_context = {
|
189
|
+
"user_request": instruction,
|
190
|
+
"history": messages[:-1],
|
191
|
+
"available_tools": ["django_chat"]
|
192
|
+
}
|
193
|
+
rendered_prompt = self.render_prompt("django_chat_prompt.j2", prompt_context)
|
194
|
+
# Simulate progressive spinner for a few cycles
|
195
|
+
for _ in range(3):
|
196
|
+
now = time.time()
|
197
|
+
if now - last_spinner_time >= spinner_yield_interval:
|
198
|
+
taking_long = (now - start_time > 10)
|
199
|
+
spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
|
200
|
+
yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
|
201
|
+
spinner_idx += 1
|
202
|
+
last_spinner_time = now
|
203
|
+
yielded_spinner = True
|
204
|
+
await asyncio.sleep(0.2)
|
205
|
+
# Final result
|
206
|
+
summary = ux.summary("Operation", 1, {"instruction": instruction[:40]})
|
207
|
+
box = ux.ansi_emoji_box(
|
208
|
+
title="DjangoChat Result",
|
209
|
+
content=f"[DjangoChat LLM] Would respond to: {rendered_prompt}",
|
210
|
+
summary=summary,
|
211
|
+
params={"instruction": instruction[:40]},
|
212
|
+
result_count=1,
|
213
|
+
op_type="run",
|
214
|
+
status="success"
|
215
|
+
)
|
216
|
+
yield {"messages": [{"role": "assistant", "content": box}]}
|
217
|
+
except Exception as e:
|
218
|
+
logger.error(f"Error during DjangoChat run: {e}", exc_info=True)
|
219
|
+
yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
|
123
220
|
|
124
221
|
def run_with_context(self, messages: List[Dict[str, str]], context_variables: dict) -> dict:
|
125
222
|
"""Minimal implementation for CLI compatibility without agents."""
|
@@ -137,6 +234,35 @@ if __name__ == "__main__":
|
|
137
234
|
]
|
138
235
|
blueprint = DjangoChatBlueprint(blueprint_id="demo-1")
|
139
236
|
async def run_and_print():
|
140
|
-
|
141
|
-
|
237
|
+
spinner = DjangoChatSpinner()
|
238
|
+
spinner.start()
|
239
|
+
try:
|
240
|
+
all_results = []
|
241
|
+
async for response in blueprint.run(messages):
|
242
|
+
content = response["messages"][0]["content"] if (isinstance(response, dict) and "messages" in response and response["messages"]) else str(response)
|
243
|
+
all_results.append(content)
|
244
|
+
# Enhanced progressive output
|
245
|
+
if isinstance(response, dict) and (response.get("progress") or response.get("matches")):
|
246
|
+
display_operation_box(
|
247
|
+
title="Progressive Operation",
|
248
|
+
content="\n".join(response.get("matches", [])),
|
249
|
+
style="bold cyan" if response.get("type") == "code_search" else "bold magenta",
|
250
|
+
result_count=len(response.get("matches", [])) if response.get("matches") is not None else None,
|
251
|
+
params={k: v for k, v in response.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
252
|
+
progress_line=response.get('progress'),
|
253
|
+
total_lines=response.get('total'),
|
254
|
+
spinner_state=spinner.current_spinner_state() if hasattr(spinner, 'current_spinner_state') else None,
|
255
|
+
op_type=response.get("type", "search"),
|
256
|
+
emoji="🔍" if response.get("type") == "code_search" else "🧠"
|
257
|
+
)
|
258
|
+
finally:
|
259
|
+
spinner.stop()
|
260
|
+
display_operation_box(
|
261
|
+
title="DjangoChat Output",
|
262
|
+
content="\n".join(all_results),
|
263
|
+
style="bold green",
|
264
|
+
result_count=len(all_results),
|
265
|
+
params={"prompt": messages[0]["content"]},
|
266
|
+
op_type="django_chat"
|
267
|
+
)
|
142
268
|
asyncio.run(run_and_print())
|
@@ -36,12 +36,28 @@ def list_files(directory: str = '.') -> str:
|
|
36
36
|
except Exception as e:
|
37
37
|
return f"ERROR: {e}"
|
38
38
|
def execute_shell_command(command: str) -> str:
|
39
|
-
|
39
|
+
"""
|
40
|
+
Executes a shell command and returns its stdout and stderr.
|
41
|
+
Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
|
42
|
+
"""
|
43
|
+
logger.info(f"Executing shell command: {command}")
|
40
44
|
try:
|
41
|
-
|
42
|
-
|
45
|
+
import os
|
46
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
|
47
|
+
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
|
48
|
+
output = f"Exit Code: {result.returncode}\n"
|
49
|
+
if result.stdout:
|
50
|
+
output += f"STDOUT:\n{result.stdout}\n"
|
51
|
+
if result.stderr:
|
52
|
+
output += f"STDERR:\n{result.stderr}\n"
|
53
|
+
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
54
|
+
return output.strip()
|
55
|
+
except subprocess.TimeoutExpired:
|
56
|
+
logger.error(f"Command timed out: {command}")
|
57
|
+
return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
|
43
58
|
except Exception as e:
|
44
|
-
|
59
|
+
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
60
|
+
return f"Error executing command: {e}"
|
45
61
|
read_file_tool = PatchedFunctionTool(read_file, 'read_file')
|
46
62
|
write_file_tool = PatchedFunctionTool(write_file, 'write_file')
|
47
63
|
list_files_tool = PatchedFunctionTool(list_files, 'list_files')
|
@@ -67,8 +83,16 @@ Self-healing, fileops-enabled, swarm-scalable.
|
|
67
83
|
# rue_code error handling: try/except ImportError with sys.exit(1)
|
68
84
|
|
69
85
|
class EchoCraftBlueprint(BlueprintBase):
|
70
|
-
def __init__(self, blueprint_id: str
|
71
|
-
super().__init__(blueprint_id, config_path=config_path, **kwargs)
|
86
|
+
def __init__(self, blueprint_id: str = "echocraft", config=None, config_path=None, **kwargs):
|
87
|
+
super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
|
88
|
+
self.blueprint_id = blueprint_id
|
89
|
+
self.config_path = config_path
|
90
|
+
self._config = config if config is not None else {}
|
91
|
+
self._llm_profile_name = None
|
92
|
+
self._llm_profile_data = None
|
93
|
+
self._markdown_output = None
|
94
|
+
# Add other attributes as needed for Echocraft
|
95
|
+
# ...
|
72
96
|
|
73
97
|
"""
|
74
98
|
A simple blueprint that echoes the last user message.
|
@@ -99,13 +123,29 @@ class EchoCraftBlueprint(BlueprintBase):
|
|
99
123
|
return '\n'.join(os.listdir(directory))
|
100
124
|
except Exception as e:
|
101
125
|
return f"ERROR: {e}"
|
102
|
-
def execute_shell_command(command: str) -> str:
|
103
|
-
|
126
|
+
def execute_shell_command(self, command: str) -> str:
|
127
|
+
"""
|
128
|
+
Executes a shell command and returns its stdout and stderr.
|
129
|
+
Timeout is configurable via SWARM_COMMAND_TIMEOUT (default: 60s).
|
130
|
+
"""
|
131
|
+
logger.info(f"Executing shell command: {command}")
|
104
132
|
try:
|
105
|
-
|
106
|
-
|
133
|
+
import os
|
134
|
+
timeout = int(os.getenv("SWARM_COMMAND_TIMEOUT", "60"))
|
135
|
+
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
|
136
|
+
output = f"Exit Code: {result.returncode}\n"
|
137
|
+
if result.stdout:
|
138
|
+
output += f"STDOUT:\n{result.stdout}\n"
|
139
|
+
if result.stderr:
|
140
|
+
output += f"STDERR:\n{result.stderr}\n"
|
141
|
+
logger.info(f"Command finished. Exit Code: {result.returncode}")
|
142
|
+
return output.strip()
|
143
|
+
except subprocess.TimeoutExpired:
|
144
|
+
logger.error(f"Command timed out: {command}")
|
145
|
+
return f"Error: Command timed out after {os.getenv('SWARM_COMMAND_TIMEOUT', '60')} seconds."
|
107
146
|
except Exception as e:
|
108
|
-
|
147
|
+
logger.error(f"Error executing command '{command}': {e}", exc_info=True)
|
148
|
+
return f"Error executing command: {e}"
|
109
149
|
read_file_tool = PatchedFunctionTool(read_file, 'read_file')
|
110
150
|
write_file_tool = PatchedFunctionTool(write_file, 'write_file')
|
111
151
|
list_files_tool = PatchedFunctionTool(list_files, 'list_files')
|
@@ -241,6 +281,82 @@ class EchoCraftBlueprint(BlueprintBase):
|
|
241
281
|
)
|
242
282
|
return echo_agent
|
243
283
|
|
284
|
+
# --- Spinner and ANSI/emoji operation box for unified UX (for CLI/dev runs) ---
|
285
|
+
from swarm.ux.ansi_box import ansi_box
|
286
|
+
from rich.console import Console
|
287
|
+
from rich.style import Style
|
288
|
+
from rich.text import Text
|
289
|
+
import threading
|
290
|
+
import time
|
291
|
+
|
292
|
+
class EchoCraftSpinner:
|
293
|
+
FRAMES = [
|
294
|
+
"Generating.", "Generating..", "Generating...", "Running...",
|
295
|
+
"⠋ Generating...", "⠙ Generating...", "⠹ Generating...", "⠸ Generating...",
|
296
|
+
"⠼ Generating...", "⠴ Generating...", "⠦ Generating...", "⠧ Generating...",
|
297
|
+
"⠇ Generating...", "⠏ Generating...", "🤖 Generating...", "💡 Generating...", "✨ Generating..."
|
298
|
+
]
|
299
|
+
SLOW_FRAME = "Generating... Taking longer than expected"
|
300
|
+
INTERVAL = 0.12
|
301
|
+
SLOW_THRESHOLD = 10 # seconds
|
302
|
+
|
303
|
+
def __init__(self):
|
304
|
+
self._stop_event = threading.Event()
|
305
|
+
self._thread = None
|
306
|
+
self._start_time = None
|
307
|
+
self.console = Console()
|
308
|
+
self._last_frame = None
|
309
|
+
self._last_slow = False
|
310
|
+
|
311
|
+
def start(self):
|
312
|
+
self._stop_event.clear()
|
313
|
+
self._start_time = time.time()
|
314
|
+
self._thread = threading.Thread(target=self._spin, daemon=True)
|
315
|
+
self._thread.start()
|
316
|
+
|
317
|
+
def _spin(self):
|
318
|
+
idx = 0
|
319
|
+
while not self._stop_event.is_set():
|
320
|
+
elapsed = time.time() - self._start_time
|
321
|
+
if elapsed > self.SLOW_THRESHOLD:
|
322
|
+
txt = Text(self.SLOW_FRAME, style=Style(color="yellow", bold=True))
|
323
|
+
self._last_frame = self.SLOW_FRAME
|
324
|
+
self._last_slow = True
|
325
|
+
else:
|
326
|
+
frame = self.FRAMES[idx % len(self.FRAMES)]
|
327
|
+
txt = Text(frame, style=Style(color="cyan", bold=True))
|
328
|
+
self._last_frame = frame
|
329
|
+
self._last_slow = False
|
330
|
+
self.console.print(txt, end="\r", soft_wrap=True, highlight=False)
|
331
|
+
time.sleep(self.INTERVAL)
|
332
|
+
idx += 1
|
333
|
+
self.console.print(" " * 40, end="\r") # Clear line
|
334
|
+
|
335
|
+
def stop(self, final_message="Done!"):
|
336
|
+
self._stop_event.set()
|
337
|
+
if self._thread:
|
338
|
+
self._thread.join()
|
339
|
+
self.console.print(Text(final_message, style=Style(color="green", bold=True)))
|
340
|
+
|
341
|
+
def current_spinner_state(self):
|
342
|
+
if self._last_slow:
|
343
|
+
return self.SLOW_FRAME
|
344
|
+
return self._last_frame or self.FRAMES[0]
|
345
|
+
|
346
|
+
|
347
|
+
def print_operation_box(op_type, results, params=None, result_type="echo", taking_long=False):
|
348
|
+
emoji = "🗣️" if result_type == "echo" else "🔍"
|
349
|
+
style = 'success' if result_type == "echo" else 'default'
|
350
|
+
box_title = op_type if op_type else ("EchoCraft Output" if result_type == "echo" else "Results")
|
351
|
+
summary_lines = []
|
352
|
+
count = len(results) if isinstance(results, list) else 0
|
353
|
+
summary_lines.append(f"Results: {count}")
|
354
|
+
if params:
|
355
|
+
for k, v in params.items():
|
356
|
+
summary_lines.append(f"{k.capitalize()}: {v}")
|
357
|
+
box_content = "\n".join(summary_lines + ["\n".join(map(str, results))])
|
358
|
+
ansi_box(box_title, box_content, count=count, params=params, style=style if not taking_long else 'warning', emoji=emoji)
|
359
|
+
|
244
360
|
if __name__ == "__main__":
|
245
361
|
import asyncio
|
246
362
|
import json
|
@@ -250,6 +366,19 @@ if __name__ == "__main__":
|
|
250
366
|
]
|
251
367
|
blueprint = EchoCraftBlueprint(blueprint_id="demo-1")
|
252
368
|
async def run_and_print():
|
253
|
-
|
254
|
-
|
369
|
+
spinner = EchoCraftSpinner()
|
370
|
+
spinner.start()
|
371
|
+
try:
|
372
|
+
all_results = []
|
373
|
+
async for response in blueprint.run(messages):
|
374
|
+
content = response["messages"][0]["content"]
|
375
|
+
all_results.append(content)
|
376
|
+
finally:
|
377
|
+
spinner.stop()
|
378
|
+
print_operation_box(
|
379
|
+
op_type="EchoCraft Output",
|
380
|
+
results=all_results,
|
381
|
+
params={"prompt": messages[0]["content"]},
|
382
|
+
result_type="echo"
|
383
|
+
)
|
255
384
|
asyncio.run(run_and_print())
|