aiecs 1.1.0__py3-none-any.whl → 1.2.0__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.

Potentially problematic release.


This version of aiecs might be problematic. Click here for more details.

Files changed (58) hide show
  1. aiecs/__init__.py +1 -1
  2. aiecs/config/config.py +2 -0
  3. aiecs/domain/__init__.py +95 -0
  4. aiecs/domain/community/__init__.py +159 -0
  5. aiecs/domain/community/agent_adapter.py +516 -0
  6. aiecs/domain/community/analytics.py +465 -0
  7. aiecs/domain/community/collaborative_workflow.py +99 -7
  8. aiecs/domain/community/communication_hub.py +649 -0
  9. aiecs/domain/community/community_builder.py +322 -0
  10. aiecs/domain/community/community_integration.py +365 -12
  11. aiecs/domain/community/community_manager.py +481 -5
  12. aiecs/domain/community/decision_engine.py +459 -13
  13. aiecs/domain/community/exceptions.py +238 -0
  14. aiecs/domain/community/models/__init__.py +36 -0
  15. aiecs/domain/community/resource_manager.py +1 -1
  16. aiecs/domain/community/shared_context_manager.py +621 -0
  17. aiecs/domain/context/context_engine.py +37 -33
  18. aiecs/main.py +2 -2
  19. aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
  20. aiecs/scripts/aid/__init__.py +15 -0
  21. aiecs/scripts/aid/version_manager.py +224 -0
  22. aiecs/scripts/dependance_check/download_nlp_data.py +1 -0
  23. aiecs/tools/__init__.py +23 -23
  24. aiecs/tools/docs/__init__.py +5 -2
  25. aiecs/tools/docs/ai_document_orchestrator.py +39 -26
  26. aiecs/tools/docs/ai_document_writer_orchestrator.py +61 -38
  27. aiecs/tools/docs/content_insertion_tool.py +48 -28
  28. aiecs/tools/docs/document_creator_tool.py +47 -29
  29. aiecs/tools/docs/document_layout_tool.py +35 -20
  30. aiecs/tools/docs/document_parser_tool.py +56 -36
  31. aiecs/tools/docs/document_writer_tool.py +115 -62
  32. aiecs/tools/schema_generator.py +56 -56
  33. aiecs/tools/statistics/__init__.py +82 -0
  34. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +581 -0
  35. aiecs/tools/statistics/ai_insight_generator_tool.py +473 -0
  36. aiecs/tools/statistics/ai_report_orchestrator_tool.py +629 -0
  37. aiecs/tools/statistics/data_loader_tool.py +518 -0
  38. aiecs/tools/statistics/data_profiler_tool.py +599 -0
  39. aiecs/tools/statistics/data_transformer_tool.py +531 -0
  40. aiecs/tools/statistics/data_visualizer_tool.py +460 -0
  41. aiecs/tools/statistics/model_trainer_tool.py +470 -0
  42. aiecs/tools/statistics/statistical_analyzer_tool.py +426 -0
  43. aiecs/tools/task_tools/chart_tool.py +2 -1
  44. aiecs/tools/task_tools/image_tool.py +43 -43
  45. aiecs/tools/task_tools/office_tool.py +39 -36
  46. aiecs/tools/task_tools/pandas_tool.py +37 -33
  47. aiecs/tools/task_tools/report_tool.py +67 -56
  48. aiecs/tools/task_tools/research_tool.py +32 -31
  49. aiecs/tools/task_tools/scraper_tool.py +53 -46
  50. aiecs/tools/task_tools/search_tool.py +1123 -0
  51. aiecs/tools/task_tools/stats_tool.py +20 -15
  52. {aiecs-1.1.0.dist-info → aiecs-1.2.0.dist-info}/METADATA +5 -1
  53. {aiecs-1.1.0.dist-info → aiecs-1.2.0.dist-info}/RECORD +57 -36
  54. {aiecs-1.1.0.dist-info → aiecs-1.2.0.dist-info}/entry_points.txt +1 -0
  55. aiecs/tools/task_tools/search_api.py +0 -7
  56. {aiecs-1.1.0.dist-info → aiecs-1.2.0.dist-info}/WHEEL +0 -0
  57. {aiecs-1.1.0.dist-info → aiecs-1.2.0.dist-info}/licenses/LICENSE +0 -0
  58. {aiecs-1.1.0.dist-info → aiecs-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,649 @@
1
+ """
2
+ Communication Hub
3
+
4
+ Provides agent-to-agent messaging, event bus, and pub/sub system
5
+ for community communication and collaboration.
6
+ """
7
+
8
+ import logging
9
+ import asyncio
10
+ from datetime import datetime
11
+ from typing import Dict, List, Any, Optional, Set, Callable
12
+ from enum import Enum
13
+ from collections import defaultdict, deque
14
+ import uuid
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class MessageType(str, Enum):
20
+ """Types of messages in the community."""
21
+ REQUEST = "request"
22
+ RESPONSE = "response"
23
+ NOTIFICATION = "notification"
24
+ SHARE = "share"
25
+ BROADCAST = "broadcast"
26
+
27
+
28
+ class EventType(str, Enum):
29
+ """Types of community events."""
30
+ COMMUNITY_CREATED = "community_created"
31
+ COMMUNITY_UPDATED = "community_updated"
32
+ MEMBER_JOINED = "member_joined"
33
+ MEMBER_EXITED = "member_exited"
34
+ MEMBER_UPDATED = "member_updated"
35
+ DECISION_PROPOSED = "decision_proposed"
36
+ DECISION_VOTED = "decision_voted"
37
+ DECISION_APPROVED = "decision_approved"
38
+ DECISION_REJECTED = "decision_rejected"
39
+ RESOURCE_CREATED = "resource_created"
40
+ RESOURCE_UPDATED = "resource_updated"
41
+ RESOURCE_SHARED = "resource_shared"
42
+ SESSION_STARTED = "session_started"
43
+ SESSION_ENDED = "session_ended"
44
+ CUSTOM = "custom"
45
+
46
+
47
+ class Message:
48
+ """Represents a message between agents."""
49
+
50
+ def __init__(
51
+ self,
52
+ sender_id: str,
53
+ recipient_ids: List[str],
54
+ message_type: MessageType,
55
+ content: Any,
56
+ metadata: Optional[Dict[str, Any]] = None
57
+ ):
58
+ """
59
+ Initialize a message.
60
+
61
+ Args:
62
+ sender_id: ID of the sending agent
63
+ recipient_ids: List of recipient agent IDs (empty for broadcast)
64
+ message_type: Type of message
65
+ content: Message content
66
+ metadata: Optional metadata
67
+ """
68
+ self.message_id = str(uuid.uuid4())
69
+ self.sender_id = sender_id
70
+ self.recipient_ids = recipient_ids
71
+ self.message_type = message_type
72
+ self.content = content
73
+ self.metadata = metadata or {}
74
+ self.timestamp = datetime.utcnow()
75
+ self.delivered_to: Set[str] = set()
76
+ self.read_by: Set[str] = set()
77
+
78
+
79
+ class Event:
80
+ """Represents a community event."""
81
+
82
+ def __init__(
83
+ self,
84
+ event_type: EventType,
85
+ source_id: str,
86
+ data: Dict[str, Any],
87
+ community_id: Optional[str] = None,
88
+ metadata: Optional[Dict[str, Any]] = None
89
+ ):
90
+ """
91
+ Initialize an event.
92
+
93
+ Args:
94
+ event_type: Type of event
95
+ source_id: ID of the source (community, member, etc.)
96
+ data: Event data
97
+ community_id: Optional community ID
98
+ metadata: Optional metadata
99
+ """
100
+ self.event_id = str(uuid.uuid4())
101
+ self.event_type = event_type
102
+ self.source_id = source_id
103
+ self.data = data
104
+ self.community_id = community_id
105
+ self.metadata = metadata or {}
106
+ self.timestamp = datetime.utcnow()
107
+
108
+
109
+ class CommunicationHub:
110
+ """
111
+ Central hub for agent communication and event distribution.
112
+ Provides messaging, pub/sub, and event broadcasting capabilities.
113
+ """
114
+
115
+ def __init__(self, max_queue_size: int = 1000):
116
+ """
117
+ Initialize the communication hub.
118
+
119
+ Args:
120
+ max_queue_size: Maximum size for message queues
121
+ """
122
+ self.max_queue_size = max_queue_size
123
+
124
+ # Message queues per agent
125
+ self.message_queues: Dict[str, deque] = defaultdict(lambda: deque(maxlen=max_queue_size))
126
+
127
+ # Event subscriptions: event_type -> set of subscriber_ids
128
+ self.event_subscriptions: Dict[EventType, Set[str]] = defaultdict(set)
129
+
130
+ # Topic subscriptions: topic -> set of subscriber_ids
131
+ self.topic_subscriptions: Dict[str, Set[str]] = defaultdict(set)
132
+
133
+ # Event handlers: subscriber_id -> handler_function
134
+ self.event_handlers: Dict[str, Callable] = {}
135
+
136
+ # Message history (limited)
137
+ self.message_history: deque = deque(maxlen=max_queue_size * 2)
138
+ self.event_history: deque = deque(maxlen=max_queue_size * 2)
139
+
140
+ # Output streams: agent_id -> list of subscriber_callback
141
+ self.output_streams: Dict[str, List[Callable]] = defaultdict(list)
142
+
143
+ # Active connections
144
+ self.active_agents: Set[str] = set()
145
+
146
+ logger.info("Communication hub initialized")
147
+
148
+ # ========== Agent Messaging ==========
149
+
150
+ async def send_message(
151
+ self,
152
+ sender_id: str,
153
+ recipient_ids: List[str],
154
+ message_type: MessageType,
155
+ content: Any,
156
+ metadata: Optional[Dict[str, Any]] = None
157
+ ) -> str:
158
+ """
159
+ Send a message to one or more recipients (unicast/multicast).
160
+
161
+ Args:
162
+ sender_id: ID of the sending agent
163
+ recipient_ids: List of recipient agent IDs
164
+ message_type: Type of message
165
+ content: Message content
166
+ metadata: Optional metadata
167
+
168
+ Returns:
169
+ Message ID
170
+ """
171
+ message = Message(sender_id, recipient_ids, message_type, content, metadata)
172
+
173
+ # Deliver to each recipient's queue
174
+ for recipient_id in recipient_ids:
175
+ if recipient_id in self.active_agents or len(self.message_queues[recipient_id]) < self.max_queue_size:
176
+ self.message_queues[recipient_id].append(message)
177
+ message.delivered_to.add(recipient_id)
178
+
179
+ # Store in history
180
+ self.message_history.append(message)
181
+
182
+ logger.debug(f"Message {message.message_id} from {sender_id} to {len(recipient_ids)} recipients")
183
+ return message.message_id
184
+
185
+ async def broadcast_message(
186
+ self,
187
+ sender_id: str,
188
+ content: Any,
189
+ community_id: Optional[str] = None,
190
+ metadata: Optional[Dict[str, Any]] = None
191
+ ) -> str:
192
+ """
193
+ Broadcast a message to all active agents or community members.
194
+
195
+ Args:
196
+ sender_id: ID of the sending agent
197
+ content: Message content
198
+ community_id: Optional community to limit broadcast
199
+ metadata: Optional metadata
200
+
201
+ Returns:
202
+ Message ID
203
+ """
204
+ # Get recipients (all active agents or community members)
205
+ recipients = list(self.active_agents)
206
+
207
+ message = Message(sender_id, recipients, MessageType.BROADCAST, content, metadata)
208
+
209
+ # Deliver to all recipients
210
+ for recipient_id in recipients:
211
+ if recipient_id != sender_id: # Don't send to self
212
+ self.message_queues[recipient_id].append(message)
213
+ message.delivered_to.add(recipient_id)
214
+
215
+ # Store in history
216
+ self.message_history.append(message)
217
+
218
+ logger.info(f"Broadcast message {message.message_id} from {sender_id} to {len(recipients)} agents")
219
+ return message.message_id
220
+
221
+ async def receive_messages(
222
+ self,
223
+ agent_id: str,
224
+ mark_as_read: bool = True,
225
+ limit: Optional[int] = None
226
+ ) -> List[Message]:
227
+ """
228
+ Receive messages for an agent.
229
+
230
+ Args:
231
+ agent_id: ID of the agent receiving messages
232
+ mark_as_read: Whether to mark messages as read
233
+ limit: Optional limit on number of messages to retrieve
234
+
235
+ Returns:
236
+ List of messages
237
+ """
238
+ queue = self.message_queues[agent_id]
239
+
240
+ if not queue:
241
+ return []
242
+
243
+ # Get messages
244
+ count = min(limit, len(queue)) if limit else len(queue)
245
+ messages = []
246
+
247
+ for _ in range(count):
248
+ if queue:
249
+ message = queue.popleft()
250
+ if mark_as_read:
251
+ message.read_by.add(agent_id)
252
+ messages.append(message)
253
+
254
+ logger.debug(f"Agent {agent_id} received {len(messages)} messages")
255
+ return messages
256
+
257
+ async def peek_messages(
258
+ self,
259
+ agent_id: str,
260
+ limit: Optional[int] = None
261
+ ) -> List[Message]:
262
+ """
263
+ Peek at messages without removing them from queue.
264
+
265
+ Args:
266
+ agent_id: ID of the agent
267
+ limit: Optional limit on number of messages
268
+
269
+ Returns:
270
+ List of messages
271
+ """
272
+ queue = self.message_queues[agent_id]
273
+ count = min(limit, len(queue)) if limit else len(queue)
274
+ return list(queue)[:count] if count > 0 else []
275
+
276
+ def get_unread_count(self, agent_id: str) -> int:
277
+ """
278
+ Get count of unread messages for an agent.
279
+
280
+ Args:
281
+ agent_id: ID of the agent
282
+
283
+ Returns:
284
+ Number of unread messages
285
+ """
286
+ return len(self.message_queues[agent_id])
287
+
288
+ # ========== Event Bus & Pub/Sub ==========
289
+
290
+ async def subscribe_to_event(
291
+ self,
292
+ subscriber_id: str,
293
+ event_type: EventType,
294
+ handler: Optional[Callable] = None
295
+ ) -> bool:
296
+ """
297
+ Subscribe to a specific event type.
298
+
299
+ Args:
300
+ subscriber_id: ID of the subscriber
301
+ event_type: Type of event to subscribe to
302
+ handler: Optional handler function for the event
303
+
304
+ Returns:
305
+ True if subscription was successful
306
+ """
307
+ self.event_subscriptions[event_type].add(subscriber_id)
308
+
309
+ if handler:
310
+ self.event_handlers[f"{subscriber_id}:{event_type.value}"] = handler
311
+
312
+ logger.debug(f"Agent {subscriber_id} subscribed to {event_type.value}")
313
+ return True
314
+
315
+ async def unsubscribe_from_event(
316
+ self,
317
+ subscriber_id: str,
318
+ event_type: EventType
319
+ ) -> bool:
320
+ """
321
+ Unsubscribe from an event type.
322
+
323
+ Args:
324
+ subscriber_id: ID of the subscriber
325
+ event_type: Type of event to unsubscribe from
326
+
327
+ Returns:
328
+ True if unsubscription was successful
329
+ """
330
+ if event_type in self.event_subscriptions:
331
+ self.event_subscriptions[event_type].discard(subscriber_id)
332
+
333
+ handler_key = f"{subscriber_id}:{event_type.value}"
334
+ if handler_key in self.event_handlers:
335
+ del self.event_handlers[handler_key]
336
+
337
+ logger.debug(f"Agent {subscriber_id} unsubscribed from {event_type.value}")
338
+ return True
339
+
340
+ async def subscribe_to_topic(
341
+ self,
342
+ subscriber_id: str,
343
+ topic: str,
344
+ handler: Optional[Callable] = None
345
+ ) -> bool:
346
+ """
347
+ Subscribe to a custom topic.
348
+
349
+ Args:
350
+ subscriber_id: ID of the subscriber
351
+ topic: Topic name
352
+ handler: Optional handler function
353
+
354
+ Returns:
355
+ True if subscription was successful
356
+ """
357
+ self.topic_subscriptions[topic].add(subscriber_id)
358
+
359
+ if handler:
360
+ self.event_handlers[f"{subscriber_id}:topic:{topic}"] = handler
361
+
362
+ logger.debug(f"Agent {subscriber_id} subscribed to topic '{topic}'")
363
+ return True
364
+
365
+ async def unsubscribe_from_topic(
366
+ self,
367
+ subscriber_id: str,
368
+ topic: str
369
+ ) -> bool:
370
+ """
371
+ Unsubscribe from a topic.
372
+
373
+ Args:
374
+ subscriber_id: ID of the subscriber
375
+ topic: Topic name
376
+
377
+ Returns:
378
+ True if unsubscription was successful
379
+ """
380
+ if topic in self.topic_subscriptions:
381
+ self.topic_subscriptions[topic].discard(subscriber_id)
382
+
383
+ handler_key = f"{subscriber_id}:topic:{topic}"
384
+ if handler_key in self.event_handlers:
385
+ del self.event_handlers[handler_key]
386
+
387
+ logger.debug(f"Agent {subscriber_id} unsubscribed from topic '{topic}'")
388
+ return True
389
+
390
+ async def publish_event(
391
+ self,
392
+ event_type: EventType,
393
+ source_id: str,
394
+ data: Dict[str, Any],
395
+ community_id: Optional[str] = None,
396
+ metadata: Optional[Dict[str, Any]] = None
397
+ ) -> str:
398
+ """
399
+ Publish an event to all subscribers.
400
+
401
+ Args:
402
+ event_type: Type of event
403
+ source_id: ID of the event source
404
+ data: Event data
405
+ community_id: Optional community ID
406
+ metadata: Optional metadata
407
+
408
+ Returns:
409
+ Event ID
410
+ """
411
+ event = Event(event_type, source_id, data, community_id, metadata)
412
+
413
+ # Store in history
414
+ self.event_history.append(event)
415
+
416
+ # Get subscribers
417
+ subscribers = self.event_subscriptions.get(event_type, set())
418
+
419
+ # Execute handlers
420
+ for subscriber_id in subscribers:
421
+ handler_key = f"{subscriber_id}:{event_type.value}"
422
+ handler = self.event_handlers.get(handler_key)
423
+
424
+ if handler:
425
+ try:
426
+ if asyncio.iscoroutinefunction(handler):
427
+ await handler(event)
428
+ else:
429
+ handler(event)
430
+ except Exception as e:
431
+ logger.error(f"Error executing event handler for {subscriber_id}: {e}")
432
+
433
+ logger.debug(f"Published event {event_type.value} to {len(subscribers)} subscribers")
434
+ return event.event_id
435
+
436
+ async def publish_to_topic(
437
+ self,
438
+ topic: str,
439
+ source_id: str,
440
+ data: Dict[str, Any],
441
+ metadata: Optional[Dict[str, Any]] = None
442
+ ) -> str:
443
+ """
444
+ Publish data to a topic.
445
+
446
+ Args:
447
+ topic: Topic name
448
+ source_id: ID of the publisher
449
+ data: Data to publish
450
+ metadata: Optional metadata
451
+
452
+ Returns:
453
+ Event ID
454
+ """
455
+ event = Event(EventType.CUSTOM, source_id, data, None, metadata)
456
+ event.metadata["topic"] = topic
457
+
458
+ # Store in history
459
+ self.event_history.append(event)
460
+
461
+ # Get subscribers
462
+ subscribers = self.topic_subscriptions.get(topic, set())
463
+
464
+ # Execute handlers
465
+ for subscriber_id in subscribers:
466
+ handler_key = f"{subscriber_id}:topic:{topic}"
467
+ handler = self.event_handlers.get(handler_key)
468
+
469
+ if handler:
470
+ try:
471
+ if asyncio.iscoroutinefunction(handler):
472
+ await handler(event)
473
+ else:
474
+ handler(event)
475
+ except Exception as e:
476
+ logger.error(f"Error executing topic handler for {subscriber_id}: {e}")
477
+
478
+ logger.debug(f"Published to topic '{topic}' for {len(subscribers)} subscribers")
479
+ return event.event_id
480
+
481
+ # ========== Output Streaming ==========
482
+
483
+ async def subscribe_to_output(
484
+ self,
485
+ publisher_id: str,
486
+ subscriber_callback: Callable[[Dict[str, Any]], None]
487
+ ) -> bool:
488
+ """
489
+ Subscribe to an agent's output stream.
490
+
491
+ Args:
492
+ publisher_id: ID of the agent publishing output
493
+ subscriber_callback: Callback function for output data
494
+
495
+ Returns:
496
+ True if subscription was successful
497
+ """
498
+ self.output_streams[publisher_id].append(subscriber_callback)
499
+ logger.debug(f"Subscribed to output stream of {publisher_id}")
500
+ return True
501
+
502
+ async def unsubscribe_from_output(
503
+ self,
504
+ publisher_id: str,
505
+ subscriber_callback: Callable[[Dict[str, Any]], None]
506
+ ) -> bool:
507
+ """
508
+ Unsubscribe from an agent's output stream.
509
+
510
+ Args:
511
+ publisher_id: ID of the agent
512
+ subscriber_callback: Callback function to remove
513
+
514
+ Returns:
515
+ True if unsubscription was successful
516
+ """
517
+ if publisher_id in self.output_streams:
518
+ if subscriber_callback in self.output_streams[publisher_id]:
519
+ self.output_streams[publisher_id].remove(subscriber_callback)
520
+ logger.debug(f"Unsubscribed from output stream of {publisher_id}")
521
+ return True
522
+ return False
523
+
524
+ async def stream_output(
525
+ self,
526
+ publisher_id: str,
527
+ output_data: Dict[str, Any],
528
+ stream_type: str = "result"
529
+ ) -> int:
530
+ """
531
+ Stream output to all subscribers with backpressure handling.
532
+
533
+ Args:
534
+ publisher_id: ID of the publishing agent
535
+ output_data: Output data to stream
536
+ stream_type: Type of output (result, progress, partial, etc.)
537
+
538
+ Returns:
539
+ Number of subscribers notified
540
+ """
541
+ if publisher_id not in self.output_streams:
542
+ return 0
543
+
544
+ stream_data = {
545
+ "publisher_id": publisher_id,
546
+ "stream_type": stream_type,
547
+ "data": output_data,
548
+ "timestamp": datetime.utcnow().isoformat()
549
+ }
550
+
551
+ subscribers = self.output_streams[publisher_id]
552
+ notified_count = 0
553
+
554
+ for callback in subscribers:
555
+ try:
556
+ if asyncio.iscoroutinefunction(callback):
557
+ await callback(stream_data)
558
+ else:
559
+ callback(stream_data)
560
+ notified_count += 1
561
+ except Exception as e:
562
+ logger.error(f"Error streaming output to subscriber: {e}")
563
+
564
+ return notified_count
565
+
566
+ # ========== Connection Management ==========
567
+
568
+ async def register_agent(self, agent_id: str) -> bool:
569
+ """
570
+ Register an agent as active in the hub.
571
+
572
+ Args:
573
+ agent_id: ID of the agent
574
+
575
+ Returns:
576
+ True if registration was successful
577
+ """
578
+ self.active_agents.add(agent_id)
579
+ logger.info(f"Registered agent {agent_id} in communication hub")
580
+ return True
581
+
582
+ async def unregister_agent(self, agent_id: str) -> bool:
583
+ """
584
+ Unregister an agent from the hub.
585
+
586
+ Args:
587
+ agent_id: ID of the agent
588
+
589
+ Returns:
590
+ True if unregistration was successful
591
+ """
592
+ self.active_agents.discard(agent_id)
593
+
594
+ # Clean up subscriptions
595
+ for event_type in self.event_subscriptions:
596
+ self.event_subscriptions[event_type].discard(agent_id)
597
+
598
+ for topic in self.topic_subscriptions:
599
+ self.topic_subscriptions[topic].discard(agent_id)
600
+
601
+ # Clean up output streams
602
+ if agent_id in self.output_streams:
603
+ del self.output_streams[agent_id]
604
+
605
+ logger.info(f"Unregistered agent {agent_id} from communication hub")
606
+ return True
607
+
608
+ # ========== Statistics & Monitoring ==========
609
+
610
+ def get_statistics(self) -> Dict[str, Any]:
611
+ """
612
+ Get communication hub statistics.
613
+
614
+ Returns:
615
+ Statistics dictionary
616
+ """
617
+ return {
618
+ "active_agents": len(self.active_agents),
619
+ "total_messages": len(self.message_history),
620
+ "total_events": len(self.event_history),
621
+ "pending_messages": sum(len(q) for q in self.message_queues.values()),
622
+ "event_subscriptions": sum(len(s) for s in self.event_subscriptions.values()),
623
+ "topic_subscriptions": sum(len(s) for s in self.topic_subscriptions.values()),
624
+ "output_streams": len(self.output_streams)
625
+ }
626
+
627
+ def get_agent_status(self, agent_id: str) -> Dict[str, Any]:
628
+ """
629
+ Get status for a specific agent.
630
+
631
+ Args:
632
+ agent_id: ID of the agent
633
+
634
+ Returns:
635
+ Agent status dictionary
636
+ """
637
+ return {
638
+ "agent_id": agent_id,
639
+ "is_active": agent_id in self.active_agents,
640
+ "unread_messages": len(self.message_queues[agent_id]),
641
+ "event_subscriptions": [
642
+ et.value for et, subs in self.event_subscriptions.items() if agent_id in subs
643
+ ],
644
+ "topic_subscriptions": [
645
+ topic for topic, subs in self.topic_subscriptions.items() if agent_id in subs
646
+ ],
647
+ "output_subscribers": len(self.output_streams.get(agent_id, []))
648
+ }
649
+