solace-agent-mesh 1.3.3__py3-none-any.whl → 1.4.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 solace-agent-mesh might be problematic. Click here for more details.

Files changed (80) hide show
  1. solace_agent_mesh/agent/adk/setup.py +183 -8
  2. solace_agent_mesh/agent/sac/app.py +337 -622
  3. solace_agent_mesh/agent/sac/component.py +47 -1
  4. solace_agent_mesh/agent/tools/dynamic_tool.py +36 -5
  5. solace_agent_mesh/agent/tools/tool_config_types.py +58 -0
  6. solace_agent_mesh/assets/docs/404.html +3 -3
  7. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.508ae8db.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/9a09e75d.92de8cf5.js +1 -0
  9. solace_agent_mesh/assets/docs/assets/js/{main.e82b32e6.js → main.1de3da6a.js} +2 -2
  10. solace_agent_mesh/assets/docs/assets/js/{runtime~main.aad1f874.js → runtime~main.3188e049.js} +1 -1
  11. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
  12. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +3 -3
  13. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  14. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
  15. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  16. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  17. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
  18. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  19. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  20. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  21. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  22. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  23. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  24. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  25. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  27. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  28. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -5
  44. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  45. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +68 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  49. solace_agent_mesh/assets/docs/lunr-index-1757991496554.json +1 -0
  50. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  51. solace_agent_mesh/assets/docs/search-doc-1757991496554.json +1 -0
  52. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  53. solace_agent_mesh/cli/__init__.py +1 -1
  54. solace_agent_mesh/cli/commands/run_cmd.py +4 -7
  55. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-CAX9u8a7.js → authCallback-j1LW-wlq.js} +1 -1
  56. solace_agent_mesh/client/webui/frontend/static/assets/{client-DXU9SPI5.js → client-B9p_nFNA.js} +1 -1
  57. solace_agent_mesh/client/webui/frontend/static/assets/main-B9s_V9tJ.css +1 -0
  58. solace_agent_mesh/client/webui/frontend/static/assets/main-Dq4AJNvn.js +339 -0
  59. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-B0BEKoAR.js → vendor-CS5YMf8a.js} +74 -69
  60. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  61. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  62. solace_agent_mesh/common/utils/pydantic_utils.py +56 -0
  63. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +6 -4
  64. solace_agent_mesh/gateway/base/app.py +58 -120
  65. solace_agent_mesh/gateway/http_sse/app.py +99 -150
  66. solace_agent_mesh/gateway/http_sse/component.py +57 -30
  67. solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +87 -0
  68. solace_agent_mesh/gateway/http_sse/sse_manager.py +44 -23
  69. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.0.dist-info}/METADATA +1 -1
  70. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.0.dist-info}/RECORD +74 -71
  71. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.3f34bf76.js +0 -1
  72. solace_agent_mesh/assets/docs/assets/js/9a09e75d.5a319fd4.js +0 -1
  73. solace_agent_mesh/assets/docs/lunr-index-1757873594308.json +0 -1
  74. solace_agent_mesh/assets/docs/search-doc-1757873594308.json +0 -1
  75. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +0 -1
  76. solace_agent_mesh/client/webui/frontend/static/assets/main-DjoMeldu.js +0 -339
  77. /solace_agent_mesh/assets/docs/assets/js/{main.e82b32e6.js.LICENSE.txt → main.1de3da6a.js.LICENSE.txt} +0 -0
  78. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.0.dist-info}/WHEEL +0 -0
  79. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.0.dist-info}/entry_points.txt +0 -0
  80. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -17,12 +17,14 @@ from fastapi import Request as FastAPIRequest
17
17
  from solace_ai_connector.common.log import log
18
18
  from solace_ai_connector.components.inputs_outputs.broker_input import BrokerInput
19
19
  from solace_ai_connector.flow.app import App as SACApp
20
+ from solace_ai_connector.common.event import Event, EventType
20
21
 
21
22
  from ...common.agent_registry import AgentRegistry
22
23
  from ...core_a2a.service import CoreA2AService
23
24
  from ...gateway.base.component import BaseGatewayComponent
24
25
  from ...gateway.http_sse.session_manager import SessionManager
25
26
  from ...gateway.http_sse.sse_manager import SSEManager
27
+ from .sse_event_buffer import SSEEventBuffer
26
28
  from .components import VisualizationForwarderComponent
27
29
 
28
30
  try:
@@ -118,8 +120,23 @@ class WebUIBackendComponent(BaseGatewayComponent):
118
120
  raise ValueError(f"Configuration retrieval error: {e}") from e
119
121
 
120
122
  sse_max_queue_size = self.get_config("sse_max_queue_size", 200)
123
+ sse_buffer_max_age_seconds = self.get_config("sse_buffer_max_age_seconds", 600)
121
124
 
122
- self.sse_manager = SSEManager(max_queue_size=sse_max_queue_size)
125
+ self.sse_event_buffer = SSEEventBuffer(
126
+ max_queue_size=sse_max_queue_size,
127
+ max_age_seconds=sse_buffer_max_age_seconds,
128
+ )
129
+ self.sse_manager = SSEManager(
130
+ max_queue_size=sse_max_queue_size, event_buffer=self.sse_event_buffer
131
+ )
132
+
133
+ self._sse_cleanup_timer_id = f"sse_cleanup_{self.gateway_id}"
134
+ cleanup_interval_sec = self.get_config("sse_buffer_cleanup_interval_seconds", 300)
135
+ self.add_timer(
136
+ delay_ms=cleanup_interval_sec * 1000,
137
+ timer_id=self._sse_cleanup_timer_id,
138
+ interval_ms=cleanup_interval_sec * 1000,
139
+ )
123
140
 
124
141
  session_config = self._resolve_session_config()
125
142
  if session_config.get("type") == "sql":
@@ -167,6 +184,15 @@ class WebUIBackendComponent(BaseGatewayComponent):
167
184
 
168
185
  log.info("%s Web UI Backend Component initialized.", self.log_identifier)
169
186
 
187
+ def process_event(self, event: Event):
188
+ if event.event_type == EventType.TIMER:
189
+ if event.data.get("timer_id") == self._sse_cleanup_timer_id:
190
+ log.debug("%s SSE buffer cleanup timer triggered.", self.log_identifier)
191
+ self.sse_event_buffer.cleanup_stale_buffers()
192
+ return
193
+
194
+ super().process_event(event)
195
+
170
196
  def _get_visualization_lock(self) -> asyncio.Lock:
171
197
  """Get or create a visualization lock for the current event loop."""
172
198
  try:
@@ -1025,6 +1051,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
1025
1051
  def cleanup(self):
1026
1052
  """Gracefully shuts down the component and the FastAPI server."""
1027
1053
  log.info("%s Cleaning up Web UI Backend Component...", self.log_identifier)
1054
+ self.cancel_timer(self._sse_cleanup_timer_id)
1028
1055
  log.info("%s Cleaning up visualization resources...", self.log_identifier)
1029
1056
  if self._visualization_message_queue:
1030
1057
  self._visualization_message_queue.put(None)
@@ -1056,6 +1083,35 @@ class WebUIBackendComponent(BaseGatewayComponent):
1056
1083
  self._cleanup_visualization_locks()
1057
1084
  log.info("%s Visualization resources cleaned up.", self.log_identifier)
1058
1085
 
1086
+ super().cleanup()
1087
+
1088
+ if self.fastapi_thread and self.fastapi_thread.is_alive():
1089
+ log.info(
1090
+ "%s Waiting for FastAPI server thread to exit...", self.log_identifier
1091
+ )
1092
+ self.fastapi_thread.join(timeout=10)
1093
+ if self.fastapi_thread.is_alive():
1094
+ log.warning(
1095
+ "%s FastAPI server thread did not exit gracefully.",
1096
+ self.log_identifier,
1097
+ )
1098
+
1099
+ if self.sse_manager:
1100
+ log.info(
1101
+ "%s Closing active SSE connections (best effort)...",
1102
+ self.log_identifier,
1103
+ )
1104
+ try:
1105
+ asyncio.run(self.sse_manager.close_all())
1106
+ except Exception as sse_close_err:
1107
+ log.error(
1108
+ "%s Error closing SSE connections during cleanup: %s",
1109
+ self.log_identifier,
1110
+ sse_close_err,
1111
+ )
1112
+
1113
+ log.info("%s Web UI Backend Component cleanup finished.", self.log_identifier)
1114
+
1059
1115
  def _infer_visualization_event_details(
1060
1116
  self, topic: str, payload: dict[str, Any]
1061
1117
  ) -> dict[str, Any]:
@@ -1302,35 +1358,6 @@ class WebUIBackendComponent(BaseGatewayComponent):
1302
1358
  )
1303
1359
  return agents
1304
1360
 
1305
- super().cleanup()
1306
-
1307
- if self.fastapi_thread and self.fastapi_thread.is_alive():
1308
- log.info(
1309
- "%s Waiting for FastAPI server thread to exit...", self.log_identifier
1310
- )
1311
- self.fastapi_thread.join(timeout=10)
1312
- if self.fastapi_thread.is_alive():
1313
- log.warning(
1314
- "%s FastAPI server thread did not exit gracefully.",
1315
- self.log_identifier,
1316
- )
1317
-
1318
- if self.sse_manager:
1319
- log.info(
1320
- "%s Closing active SSE connections (best effort)...",
1321
- self.log_identifier,
1322
- )
1323
- try:
1324
- asyncio.run(self.sse_manager.close_all())
1325
- except Exception as sse_close_err:
1326
- log.error(
1327
- "%s Error closing SSE connections during cleanup: %s",
1328
- self.log_identifier,
1329
- sse_close_err,
1330
- )
1331
-
1332
- log.info("%s Web UI Backend Component cleanup finished.", self.log_identifier)
1333
-
1334
1361
  def get_agent_registry(self) -> AgentRegistry:
1335
1362
  return self.agent_registry
1336
1363
 
@@ -0,0 +1,87 @@
1
+ """
2
+ A thread-safe buffer for holding early SSE events before a client connects.
3
+ """
4
+
5
+ import datetime
6
+ import threading
7
+ from typing import Any, Dict, List, Optional, Tuple
8
+
9
+ from solace_ai_connector.common.log import log
10
+
11
+
12
+ class SSEEventBuffer:
13
+ """Manages buffering and cleanup of SSE events for tasks without active listeners."""
14
+
15
+ def __init__(self, max_queue_size: int, max_age_seconds: int):
16
+ self._pending_events: Dict[
17
+ str, Tuple[datetime.datetime, List[Dict[str, Any]]]
18
+ ] = {}
19
+ self._lock = threading.Lock()
20
+ self._max_queue_size = max_queue_size
21
+ self._max_age_seconds = max_age_seconds
22
+ self.log_identifier = "[SSEEventBuffer]"
23
+ log.debug(
24
+ "%s Initialized with max_age:%ds, max_size:%d",
25
+ self.log_identifier,
26
+ self._max_age_seconds,
27
+ self._max_queue_size,
28
+ )
29
+
30
+ def buffer_event(self, task_id: str, event: Dict[str, Any]):
31
+ """Buffers an event for a given task ID."""
32
+ with self._lock:
33
+ if task_id not in self._pending_events:
34
+ self._pending_events[task_id] = (
35
+ datetime.datetime.now(datetime.timezone.utc),
36
+ [],
37
+ )
38
+
39
+ if len(self._pending_events[task_id][1]) < self._max_queue_size:
40
+ self._pending_events[task_id][1].append(event)
41
+ else:
42
+ log.warning(
43
+ "%s Buffer full for Task ID: %s. Event dropped.",
44
+ self.log_identifier,
45
+ task_id,
46
+ )
47
+
48
+ def get_and_remove_buffer(self, task_id: str) -> Optional[List[Dict[str, Any]]]:
49
+ """Atomically retrieves and removes the event buffer for a task."""
50
+ with self._lock:
51
+ buffer_tuple = self._pending_events.pop(task_id, None)
52
+ if buffer_tuple:
53
+ log.debug(
54
+ "%s Flushing %d events for Task ID: %s",
55
+ self.log_identifier,
56
+ len(buffer_tuple[1]),
57
+ task_id,
58
+ )
59
+ return buffer_tuple[1]
60
+ return None
61
+
62
+ def remove_buffer(self, task_id: str):
63
+ """Explicitly removes a buffer for a task, e.g., on finalization."""
64
+ with self._lock:
65
+ if self._pending_events.pop(task_id, None):
66
+ log.debug(
67
+ "%s Removed buffer for task %s.", self.log_identifier, task_id
68
+ )
69
+
70
+ def cleanup_stale_buffers(self):
71
+ """Removes all pending event buffers older than the max age."""
72
+ with self._lock:
73
+ now = datetime.datetime.now(datetime.timezone.utc)
74
+ stale_tasks = [
75
+ task_id
76
+ for task_id, (timestamp, _) in self._pending_events.items()
77
+ if (now - timestamp).total_seconds() > self._max_age_seconds
78
+ ]
79
+
80
+ if stale_tasks:
81
+ log.debug(
82
+ "%s Cleaning up %d stale event buffers.",
83
+ self.log_identifier,
84
+ len(stale_tasks),
85
+ )
86
+ for task_id in stale_tasks:
87
+ del self._pending_events[task_id]
@@ -11,6 +11,8 @@ import math
11
11
 
12
12
  from solace_ai_connector.common.log import log
13
13
 
14
+ from .sse_event_buffer import SSEEventBuffer
15
+
14
16
 
15
17
  class SSEManager:
16
18
  """
@@ -18,8 +20,9 @@ class SSEManager:
18
20
  Uses asyncio Queues for buffering events per connection.
19
21
  """
20
22
 
21
- def __init__(self, max_queue_size: int = 200):
23
+ def __init__(self, max_queue_size: int, event_buffer: SSEEventBuffer):
22
24
  self._connections: Dict[str, List[asyncio.Queue]] = {}
25
+ self._event_buffer = event_buffer
23
26
  self._locks: Dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
24
27
  self._locks_lock = threading.Lock()
25
28
  self.log_identifier = "[SSEManager]"
@@ -64,7 +67,6 @@ class SSEManager:
64
67
  else:
65
68
  return str(obj)
66
69
 
67
-
68
70
  async def create_sse_connection(self, task_id: str) -> asyncio.Queue:
69
71
  """
70
72
  Creates a new queue for an SSE connection subscribing to a task.
@@ -81,8 +83,15 @@ class SSEManager:
81
83
  self._connections[task_id] = []
82
84
 
83
85
  connection_queue = asyncio.Queue(maxsize=self._max_queue_size)
86
+
87
+ # Flush any pending events from the buffer to the new connection
88
+ buffered_events = self._event_buffer.get_and_remove_buffer(task_id)
89
+ if buffered_events:
90
+ for event in buffered_events:
91
+ await connection_queue.put(event)
92
+
84
93
  self._connections[task_id].append(connection_queue)
85
- log.info(
94
+ log.debug(
86
95
  "%s Created SSE connection queue for Task ID: %s. Total queues for task: %d",
87
96
  self.log_identifier,
88
97
  task_id,
@@ -105,7 +114,7 @@ class SSEManager:
105
114
  if task_id in self._connections:
106
115
  try:
107
116
  self._connections[task_id].remove(connection_queue)
108
- log.info(
117
+ log.debug(
109
118
  "%s Removed SSE connection queue for Task ID: %s. Remaining queues: %d",
110
119
  self.log_identifier,
111
120
  task_id,
@@ -113,7 +122,7 @@ class SSEManager:
113
122
  )
114
123
  if not self._connections[task_id]:
115
124
  del self._connections[task_id]
116
- log.info(
125
+ log.debug(
117
126
  "%s Removed Task ID entry: %s as no connections remain.",
118
127
  self.log_identifier,
119
128
  task_id,
@@ -145,19 +154,11 @@ class SSEManager:
145
154
  """
146
155
  lock = self._get_lock()
147
156
  async with lock:
148
- if task_id not in self._connections:
149
- log.debug(
150
- "%s No active SSE connections for Task ID: %s. Event not sent.",
151
- self.log_identifier,
152
- task_id,
153
- )
154
- return
157
+ queues = self._connections.get(task_id)
155
158
 
156
- queues_to_remove = []
157
159
  try:
158
160
  serialized_data = json.dumps(
159
- self._sanitize_json(event_data),
160
- allow_nan=False
161
+ self._sanitize_json(event_data), allow_nan=False
161
162
  )
162
163
  except Exception as json_err:
163
164
  log.error(
@@ -169,6 +170,16 @@ class SSEManager:
169
170
  return
170
171
 
171
172
  sse_payload = {"event": event_type, "data": serialized_data}
173
+
174
+ if not queues:
175
+ log.debug(
176
+ "%s No active SSE connections for Task ID: %s. Buffering event.",
177
+ self.log_identifier,
178
+ task_id,
179
+ )
180
+ self._event_buffer.buffer_event(task_id, sse_payload)
181
+ return
182
+
172
183
  log.debug(
173
184
  "%s Prepared SSE payload for Task ID %s: %s",
174
185
  self.log_identifier,
@@ -176,6 +187,7 @@ class SSEManager:
176
187
  sse_payload,
177
188
  )
178
189
 
190
+ queues_to_remove = []
179
191
  for connection_queue in list(self._connections.get(task_id, [])):
180
192
  try:
181
193
  await asyncio.wait_for(
@@ -224,7 +236,7 @@ class SSEManager:
224
236
 
225
237
  if not current_queues:
226
238
  del self._connections[task_id]
227
- log.info(
239
+ log.debug(
228
240
  "%s Removed Task ID entry: %s after cleaning queues.",
229
241
  self.log_identifier,
230
242
  task_id,
@@ -235,7 +247,7 @@ class SSEManager:
235
247
  Signals a specific SSE connection queue to close by putting None.
236
248
  Also removes the queue from the manager.
237
249
  """
238
- log.info(
250
+ log.debug(
239
251
  "%s Closing specific SSE connection queue for Task ID: %s",
240
252
  self.log_identifier,
241
253
  task_id,
@@ -267,13 +279,17 @@ class SSEManager:
267
279
  async def close_all_for_task(self, task_id: str):
268
280
  """
269
281
  Closes all SSE connections associated with a specific task.
282
+ If a connection existed, it also cleans up the event buffer.
283
+ If no connection ever existed, the buffer is left for a late-connecting client.
270
284
  """
271
285
  lock = self._get_lock()
272
286
  async with lock:
273
287
  if task_id in self._connections:
288
+ # This is the "normal" case: a client is or was connected.
289
+ # It's safe to clean up everything.
274
290
  queues_to_close = self._connections.pop(task_id)
275
- log.info(
276
- "%s Closing %d SSE connections for Task ID: %s",
291
+ log.debug(
292
+ "%s Closing %d SSE connections for Task ID: %s and cleaning up buffer.",
277
293
  self.log_identifier,
278
294
  len(queues_to_close),
279
295
  task_id,
@@ -300,14 +316,19 @@ class SSEManager:
300
316
  task_id,
301
317
  e,
302
318
  )
303
- log.info(
319
+
320
+ # Since a connection existed, the buffer is no longer needed.
321
+ self._event_buffer.remove_buffer(task_id)
322
+ log.debug(
304
323
  "%s Removed Task ID entry: %s and signaled queues to close.",
305
324
  self.log_identifier,
306
325
  task_id,
307
326
  )
308
327
  else:
328
+ # This is the "race condition" case: no client has connected yet.
329
+ # We MUST leave the buffer intact for the late-connecting client.
309
330
  log.debug(
310
- "%s No connections found to close for Task ID: %s",
331
+ "%s No active connections found for Task ID: %s. Leaving event buffer intact.",
311
332
  self.log_identifier,
312
333
  task_id,
313
334
  )
@@ -329,7 +350,7 @@ class SSEManager:
329
350
  self.cleanup_old_locks()
330
351
  lock = self._get_lock()
331
352
  async with lock:
332
- log.info("%s Closing all active SSE connections...", self.log_identifier)
353
+ log.debug("%s Closing all active SSE connections...", self.log_identifier)
333
354
  all_task_ids = list(self._connections.keys())
334
355
  closed_count = 0
335
356
  for task_id in all_task_ids:
@@ -341,7 +362,7 @@ class SSEManager:
341
362
  await asyncio.wait_for(q.put(None), timeout=0.1)
342
363
  except Exception:
343
364
  pass
344
- log.info(
365
+ log.debug(
345
366
  "%s Closed %d connections for tasks: %s",
346
367
  self.log_identifier,
347
368
  closed_count,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solace-agent-mesh
3
- Version: 1.3.3
3
+ Version: 1.4.0
4
4
  Summary: Solace Agent Mesh is an open-source framework for building event-driven, multi-agent AI systems where specialized agents collaborate on complex tasks.
5
5
  Project-URL: Homepage, https://github.com/SolaceLabs/solace-agent-mesh
6
6
  Project-URL: Repository, https://github.com/SolaceLabs/solace-agent-mesh