|
21 | 21 | from collections import deque |
22 | 22 | from datetime import datetime |
23 | 23 | from typing import Any, Callable, Dict, List, Optional |
24 | | - |
| 24 | +from strands import tool |
25 | 25 |
|
26 | 26 | # ── Event Types ───────────────────────────────────────────────── |
27 | 27 | # Standardized event type constants for consistency |
@@ -254,3 +254,121 @@ def emit(event_type: str, source: str, summary: str, detail: str = "", |
254 | 254 | metadata: Optional[Dict[str, Any]] = None) -> Event: |
255 | 255 | """Shorthand: push an event to the global bus.""" |
256 | 256 | return bus.emit(event_type, source, summary, detail, metadata) |
| 257 | + |
| 258 | +@tool |
| 259 | +def event_bus( |
| 260 | + action: str = "recent", |
| 261 | + event_type: str = None, |
| 262 | + source: str = "agent", |
| 263 | + summary: str = "", |
| 264 | + detail: str = "", |
| 265 | + count: int = 30, |
| 266 | + seconds: float = 300, |
| 267 | + max_age_seconds: float = 300, |
| 268 | +) -> Dict[str, Any]: |
| 269 | + """🔔 Inspect and control the unified DevDuck event bus. |
| 270 | +
|
| 271 | + All background tools (telegram, whatsapp, scheduler, tasks, listen, |
| 272 | + notify, zenoh, speech) push events here. The TUI sidebar and agent |
| 273 | + context reads from it. |
| 274 | +
|
| 275 | + Actions: |
| 276 | + - "recent": List last N events (count=30) |
| 277 | + - "by_type": Filter by event_type (e.g. "telegram.in") |
| 278 | + - "since": Events from last N seconds |
| 279 | + - "context": Formatted context block (as injected to agent) |
| 280 | + - "emit": Push a custom event (requires event_type+summary) |
| 281 | + - "clear": Wipe the buffer |
| 282 | + - "stats": Counter, size, event type distribution |
| 283 | +
|
| 284 | + Args: |
| 285 | + action: One of the actions above |
| 286 | + event_type: For "by_type" filter or "emit" (e.g. "custom.note") |
| 287 | + source: Source label when emitting (default: "agent") |
| 288 | + summary: Short one-liner (required for emit) |
| 289 | + detail: Longer detail text (optional for emit) |
| 290 | + count: How many events to return (recent/by_type) |
| 291 | + seconds: Time window for "since" |
| 292 | + max_age_seconds: Time window for "context" |
| 293 | +
|
| 294 | + Returns: |
| 295 | + Dict with status and content. |
| 296 | + """ |
| 297 | + try: |
| 298 | + if action == "recent": |
| 299 | + events = bus.recent(count=count) |
| 300 | + if not events: |
| 301 | + return {"status": "success", "content": [{"text": "No events in bus."}]} |
| 302 | + lines = [f"📋 {len(events)} recent events (total emitted: {bus.count}):"] |
| 303 | + for e in events: |
| 304 | + detail_preview = f" — {e.detail[:100]}" if e.detail else "" |
| 305 | + lines.append(f"[{e.time_str}] {e.icon} {e.source} | {e.event_type}: {e.summary}{detail_preview}") |
| 306 | + return {"status": "success", "content": [{"text": "\n".join(lines)}]} |
| 307 | + |
| 308 | + elif action == "by_type": |
| 309 | + if not event_type: |
| 310 | + return {"status": "error", "content": [{"text": "event_type required for by_type"}]} |
| 311 | + events = bus.recent_by_type(event_type, count=count) |
| 312 | + if not events: |
| 313 | + return {"status": "success", "content": [{"text": f"No events of type '{event_type}'."}]} |
| 314 | + lines = [f"📋 {len(events)} events of type '{event_type}':"] |
| 315 | + for e in events: |
| 316 | + lines.append(f"[{e.time_str}] {e.icon} {e.source}: {e.summary}") |
| 317 | + return {"status": "success", "content": [{"text": "\n".join(lines)}]} |
| 318 | + |
| 319 | + elif action == "since": |
| 320 | + events = bus.recent_since(seconds) |
| 321 | + if not events: |
| 322 | + return {"status": "success", "content": [{"text": f"No events in last {seconds}s."}]} |
| 323 | + lines = [f"📋 {len(events)} events in last {seconds}s:"] |
| 324 | + for e in events: |
| 325 | + lines.append(f"[{e.time_str}] {e.icon} {e.source} | {e.event_type}: {e.summary}") |
| 326 | + return {"status": "success", "content": [{"text": "\n".join(lines)}]} |
| 327 | + |
| 328 | + elif action == "context": |
| 329 | + ctx = bus.get_context_string(max_events=count, max_age_seconds=max_age_seconds) |
| 330 | + if not ctx: |
| 331 | + return {"status": "success", "content": [{"text": "No recent events for context."}]} |
| 332 | + return {"status": "success", "content": [{"text": ctx}]} |
| 333 | + |
| 334 | + elif action == "emit": |
| 335 | + if not event_type or not summary: |
| 336 | + return {"status": "error", "content": [{"text": "emit requires event_type + summary"}]} |
| 337 | + e = bus.emit(event_type, source, summary, detail) |
| 338 | + return {"status": "success", "content": [{"text": f"✅ Emitted [{e.time_str}] {e.icon} {source} | {event_type}: {summary}"}]} |
| 339 | + |
| 340 | + elif action == "clear": |
| 341 | + prev = bus.size |
| 342 | + bus.clear() |
| 343 | + return {"status": "success", "content": [{"text": f"🗑 Cleared {prev} events."}]} |
| 344 | + |
| 345 | + elif action == "stats": |
| 346 | + events = bus.recent(count=bus.size) |
| 347 | + type_counts: Dict[str, int] = {} |
| 348 | + source_counts: Dict[str, int] = {} |
| 349 | + for e in events: |
| 350 | + type_counts[e.event_type] = type_counts.get(e.event_type, 0) + 1 |
| 351 | + source_counts[e.source] = source_counts.get(e.source, 0) + 1 |
| 352 | + lines = [ |
| 353 | + f"📊 Event Bus Stats", |
| 354 | + f" Total emitted: {bus.count}", |
| 355 | + f" Currently buffered: {bus.size}", |
| 356 | + f" Subscribers: {len(bus._subscribers)}", |
| 357 | + "", |
| 358 | + "By type:", |
| 359 | + ] |
| 360 | + for t, c in sorted(type_counts.items(), key=lambda x: -x[1]): |
| 361 | + lines.append(f" {c:4d} {t}") |
| 362 | + lines.append("\nBy source:") |
| 363 | + for s, c in sorted(source_counts.items(), key=lambda x: -x[1]): |
| 364 | + lines.append(f" {c:4d} {s}") |
| 365 | + return {"status": "success", "content": [{"text": "\n".join(lines)}]} |
| 366 | + |
| 367 | + else: |
| 368 | + return { |
| 369 | + "status": "error", |
| 370 | + "content": [{"text": f"Unknown action '{action}'. Valid: recent, by_type, since, context, emit, clear, stats"}], |
| 371 | + } |
| 372 | + |
| 373 | + except Exception as e: |
| 374 | + return {"status": "error", "content": [{"text": f"event_bus error: {e}"}]} |
0 commit comments