fast-agent-mcp 0.3.9__py3-none-any.whl → 0.3.11__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 fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/config.py +2 -2
- fast_agent/human_input/elicitation_handler.py +71 -54
- fast_agent/mcp/elicitation_factory.py +2 -2
- fast_agent/mcp/mcp_aggregator.py +41 -44
- fast_agent/mcp/sampling.py +21 -0
- fast_agent/mcp/transport_tracking.py +3 -1
- fast_agent/ui/console_display.py +27 -22
- fast_agent/ui/elicitation_style.py +7 -7
- fast_agent/ui/enhanced_prompt.py +19 -3
- fast_agent/ui/mcp_display.py +178 -89
- fast_agent/ui/notification_tracker.py +167 -0
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.11.dist-info}/METADATA +1 -1
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.11.dist-info}/RECORD +16 -15
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.11.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.11.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.9.dist-info → fast_agent_mcp-0.3.11.dist-info}/licenses/LICENSE +0 -0
fast_agent/config.py
CHANGED
|
@@ -43,8 +43,8 @@ class MCPSamplingSettings(BaseModel):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class MCPElicitationSettings(BaseModel):
|
|
46
|
-
mode: Literal["forms", "
|
|
47
|
-
"""Elicitation mode: 'forms' (default UI), '
|
|
46
|
+
mode: Literal["forms", "auto-cancel", "none"] = "none"
|
|
47
|
+
"""Elicitation mode: 'forms' (default UI), 'auto-cancel', 'none' (no capability)"""
|
|
48
48
|
|
|
49
49
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
|
50
50
|
|
|
@@ -29,64 +29,81 @@ async def elicitation_input_callback(
|
|
|
29
29
|
)
|
|
30
30
|
effective_server_name = server_name or "Unknown Server"
|
|
31
31
|
|
|
32
|
-
#
|
|
33
|
-
|
|
32
|
+
# Start tracking elicitation operation
|
|
33
|
+
try:
|
|
34
|
+
from fast_agent.ui import notification_tracker
|
|
35
|
+
notification_tracker.start_elicitation(effective_server_name)
|
|
36
|
+
except Exception:
|
|
37
|
+
# Don't let notification tracking break elicitation
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Check if elicitation is disabled for this server
|
|
42
|
+
if elicitation_state.is_disabled(effective_server_name):
|
|
43
|
+
return HumanInputResponse(
|
|
44
|
+
request_id=request.request_id,
|
|
45
|
+
response="__CANCELLED__",
|
|
46
|
+
metadata={"auto_cancelled": True, "reason": "Server elicitation disabled by user"},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Get the elicitation schema from metadata
|
|
50
|
+
schema: Optional[Dict[str, Any]] = None
|
|
51
|
+
if request.metadata and "requested_schema" in request.metadata:
|
|
52
|
+
schema = request.metadata["requested_schema"]
|
|
53
|
+
|
|
54
|
+
# Use the context manager to pause the progress display while getting input
|
|
55
|
+
with progress_display.paused():
|
|
56
|
+
try:
|
|
57
|
+
if schema:
|
|
58
|
+
form_action, form_data = await show_simple_elicitation_form(
|
|
59
|
+
schema=schema,
|
|
60
|
+
message=request.prompt,
|
|
61
|
+
agent_name=effective_agent_name,
|
|
62
|
+
server_name=effective_server_name,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if form_action == "accept" and form_data is not None:
|
|
66
|
+
# Convert form data to JSON string
|
|
67
|
+
import json
|
|
68
|
+
|
|
69
|
+
response = json.dumps(form_data)
|
|
70
|
+
elif form_action == "decline":
|
|
71
|
+
response = "__DECLINED__"
|
|
72
|
+
elif form_action == "disable":
|
|
73
|
+
response = "__DISABLE_SERVER__"
|
|
74
|
+
else: # cancel
|
|
75
|
+
response = "__CANCELLED__"
|
|
76
|
+
else:
|
|
77
|
+
# No schema, fall back to text input using prompt_toolkit only
|
|
78
|
+
from prompt_toolkit.shortcuts import input_dialog
|
|
79
|
+
|
|
80
|
+
response = await input_dialog(
|
|
81
|
+
title="Input Requested",
|
|
82
|
+
text=f"Agent: {effective_agent_name}\nServer: {effective_server_name}\n\n{request.prompt}",
|
|
83
|
+
style=ELICITATION_STYLE,
|
|
84
|
+
).run_async()
|
|
85
|
+
|
|
86
|
+
if response is None:
|
|
87
|
+
response = "__CANCELLED__"
|
|
88
|
+
|
|
89
|
+
except KeyboardInterrupt:
|
|
90
|
+
response = "__CANCELLED__"
|
|
91
|
+
except EOFError:
|
|
92
|
+
response = "__CANCELLED__"
|
|
93
|
+
|
|
34
94
|
return HumanInputResponse(
|
|
35
95
|
request_id=request.request_id,
|
|
36
|
-
response=
|
|
37
|
-
metadata={"
|
|
96
|
+
response=response.strip() if isinstance(response, str) else response,
|
|
97
|
+
metadata={"has_schema": schema is not None},
|
|
38
98
|
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
schema: Optional[Dict[str, Any]] = None
|
|
42
|
-
if request.metadata and "requested_schema" in request.metadata:
|
|
43
|
-
schema = request.metadata["requested_schema"]
|
|
44
|
-
|
|
45
|
-
# Use the context manager to pause the progress display while getting input
|
|
46
|
-
with progress_display.paused():
|
|
99
|
+
finally:
|
|
100
|
+
# End tracking elicitation operation
|
|
47
101
|
try:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
server_name=effective_server_name,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
if form_action == "accept" and form_data is not None:
|
|
57
|
-
# Convert form data to JSON string
|
|
58
|
-
import json
|
|
59
|
-
|
|
60
|
-
response = json.dumps(form_data)
|
|
61
|
-
elif form_action == "decline":
|
|
62
|
-
response = "__DECLINED__"
|
|
63
|
-
elif form_action == "disable":
|
|
64
|
-
response = "__DISABLE_SERVER__"
|
|
65
|
-
else: # cancel
|
|
66
|
-
response = "__CANCELLED__"
|
|
67
|
-
else:
|
|
68
|
-
# No schema, fall back to text input using prompt_toolkit only
|
|
69
|
-
from prompt_toolkit.shortcuts import input_dialog
|
|
70
|
-
|
|
71
|
-
response = await input_dialog(
|
|
72
|
-
title="Input Requested",
|
|
73
|
-
text=f"Agent: {effective_agent_name}\nServer: {effective_server_name}\n\n{request.prompt}",
|
|
74
|
-
style=ELICITATION_STYLE,
|
|
75
|
-
).run_async()
|
|
76
|
-
|
|
77
|
-
if response is None:
|
|
78
|
-
response = "__CANCELLED__"
|
|
79
|
-
|
|
80
|
-
except KeyboardInterrupt:
|
|
81
|
-
response = "__CANCELLED__"
|
|
82
|
-
except EOFError:
|
|
83
|
-
response = "__CANCELLED__"
|
|
84
|
-
|
|
85
|
-
return HumanInputResponse(
|
|
86
|
-
request_id=request.request_id,
|
|
87
|
-
response=response.strip() if isinstance(response, str) else response,
|
|
88
|
-
metadata={"has_schema": schema is not None},
|
|
89
|
-
)
|
|
102
|
+
from fast_agent.ui import notification_tracker
|
|
103
|
+
notification_tracker.end_elicitation(effective_server_name)
|
|
104
|
+
except Exception:
|
|
105
|
+
# Don't let notification tracking break elicitation
|
|
106
|
+
pass
|
|
90
107
|
|
|
91
108
|
|
|
92
109
|
# Register adapter with fast_agent tools so they can invoke this UI handler without importing types
|
|
@@ -53,7 +53,7 @@ def resolve_elicitation_handler(
|
|
|
53
53
|
if mode == "none":
|
|
54
54
|
logger.debug(f"Elicitation disabled by server config for agent {agent_config.name}")
|
|
55
55
|
return None # Don't advertise elicitation capability
|
|
56
|
-
elif mode == "
|
|
56
|
+
elif mode == "auto-cancel":
|
|
57
57
|
logger.debug(
|
|
58
58
|
f"Using auto-cancel elicitation handler (server config) for agent {agent_config.name}"
|
|
59
59
|
)
|
|
@@ -74,7 +74,7 @@ def resolve_elicitation_handler(
|
|
|
74
74
|
if mode == "none":
|
|
75
75
|
logger.debug(f"Elicitation disabled by global config for agent {agent_config.name}")
|
|
76
76
|
return None # Don't advertise elicitation capability
|
|
77
|
-
elif mode == "
|
|
77
|
+
elif mode == "auto-cancel":
|
|
78
78
|
logger.debug(
|
|
79
79
|
f"Using auto-cancel elicitation handler (global config) for agent {agent_config.name}"
|
|
80
80
|
)
|
fast_agent/mcp/mcp_aggregator.py
CHANGED
|
@@ -334,6 +334,9 @@ class MCPAggregator(ContextDependent):
|
|
|
334
334
|
server_name, client_session_factory=self._create_session_factory(server_name)
|
|
335
335
|
)
|
|
336
336
|
|
|
337
|
+
# Record the initialize call that happened during connection setup
|
|
338
|
+
await self._record_server_call(server_name, "initialize", True)
|
|
339
|
+
|
|
337
340
|
logger.info(
|
|
338
341
|
f"MCP Servers initialized for agent '{self.agent_name}'",
|
|
339
342
|
data={
|
|
@@ -342,27 +345,39 @@ class MCPAggregator(ContextDependent):
|
|
|
342
345
|
},
|
|
343
346
|
)
|
|
344
347
|
|
|
345
|
-
async def fetch_tools(
|
|
348
|
+
async def fetch_tools(server_name: str) -> List[Tool]:
|
|
346
349
|
# Only fetch tools if the server supports them
|
|
347
350
|
if not await self.server_supports_feature(server_name, "tools"):
|
|
348
351
|
logger.debug(f"Server '{server_name}' does not support tools")
|
|
349
352
|
return []
|
|
350
353
|
|
|
351
354
|
try:
|
|
352
|
-
result: ListToolsResult = await
|
|
355
|
+
result: ListToolsResult = await self._execute_on_server(
|
|
356
|
+
server_name=server_name,
|
|
357
|
+
operation_type="tools/list",
|
|
358
|
+
operation_name="",
|
|
359
|
+
method_name="list_tools",
|
|
360
|
+
method_args={},
|
|
361
|
+
)
|
|
353
362
|
return result.tools or []
|
|
354
363
|
except Exception as e:
|
|
355
364
|
logger.error(f"Error loading tools from server '{server_name}'", data=e)
|
|
356
365
|
return []
|
|
357
366
|
|
|
358
|
-
async def fetch_prompts(
|
|
367
|
+
async def fetch_prompts(server_name: str) -> List[Prompt]:
|
|
359
368
|
# Only fetch prompts if the server supports them
|
|
360
369
|
if not await self.server_supports_feature(server_name, "prompts"):
|
|
361
370
|
logger.debug(f"Server '{server_name}' does not support prompts")
|
|
362
371
|
return []
|
|
363
372
|
|
|
364
373
|
try:
|
|
365
|
-
result = await
|
|
374
|
+
result = await self._execute_on_server(
|
|
375
|
+
server_name=server_name,
|
|
376
|
+
operation_type="prompts/list",
|
|
377
|
+
operation_name="",
|
|
378
|
+
method_name="list_prompts",
|
|
379
|
+
method_args={},
|
|
380
|
+
)
|
|
366
381
|
return getattr(result, "prompts", [])
|
|
367
382
|
except Exception as e:
|
|
368
383
|
logger.debug(f"Error loading prompts from server '{server_name}': {e}")
|
|
@@ -372,20 +387,9 @@ class MCPAggregator(ContextDependent):
|
|
|
372
387
|
tools: List[Tool] = []
|
|
373
388
|
prompts: List[Prompt] = []
|
|
374
389
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
)
|
|
379
|
-
tools = await fetch_tools(server_connection.session, server_name)
|
|
380
|
-
prompts = await fetch_prompts(server_connection.session, server_name)
|
|
381
|
-
else:
|
|
382
|
-
async with gen_client(
|
|
383
|
-
server_name,
|
|
384
|
-
server_registry=self.context.server_registry,
|
|
385
|
-
client_session_factory=self._create_session_factory(server_name),
|
|
386
|
-
) as client:
|
|
387
|
-
tools = await fetch_tools(client, server_name)
|
|
388
|
-
prompts = await fetch_prompts(client, server_name)
|
|
390
|
+
# Use _execute_on_server for consistent tracking regardless of connection mode
|
|
391
|
+
tools = await fetch_tools(server_name)
|
|
392
|
+
prompts = await fetch_prompts(server_name)
|
|
389
393
|
|
|
390
394
|
return server_name, tools, prompts
|
|
391
395
|
|
|
@@ -978,7 +982,7 @@ class MCPAggregator(ContextDependent):
|
|
|
978
982
|
|
|
979
983
|
return await self._execute_on_server(
|
|
980
984
|
server_name=server_name,
|
|
981
|
-
operation_type="
|
|
985
|
+
operation_type="tools/call",
|
|
982
986
|
operation_name=local_tool_name,
|
|
983
987
|
method_name="call_tool",
|
|
984
988
|
method_args={
|
|
@@ -1071,7 +1075,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1071
1075
|
|
|
1072
1076
|
result = await self._execute_on_server(
|
|
1073
1077
|
server_name=server_name,
|
|
1074
|
-
operation_type="
|
|
1078
|
+
operation_type="prompts/get",
|
|
1075
1079
|
operation_name=local_prompt_name or "default",
|
|
1076
1080
|
method_name="get_prompt",
|
|
1077
1081
|
method_args=method_args,
|
|
@@ -1119,7 +1123,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1119
1123
|
|
|
1120
1124
|
result = await self._execute_on_server(
|
|
1121
1125
|
server_name=s_name,
|
|
1122
|
-
operation_type="
|
|
1126
|
+
operation_type="prompts/get",
|
|
1123
1127
|
operation_name=local_prompt_name,
|
|
1124
1128
|
method_name="get_prompt",
|
|
1125
1129
|
method_args=method_args,
|
|
@@ -1167,7 +1171,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1167
1171
|
|
|
1168
1172
|
result = await self._execute_on_server(
|
|
1169
1173
|
server_name=s_name,
|
|
1170
|
-
operation_type="
|
|
1174
|
+
operation_type="prompts/get",
|
|
1171
1175
|
operation_name=local_prompt_name,
|
|
1172
1176
|
method_name="get_prompt",
|
|
1173
1177
|
method_args=method_args,
|
|
@@ -1190,7 +1194,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1190
1194
|
try:
|
|
1191
1195
|
prompt_list_result = await self._execute_on_server(
|
|
1192
1196
|
server_name=s_name,
|
|
1193
|
-
operation_type="prompts
|
|
1197
|
+
operation_type="prompts/list",
|
|
1194
1198
|
operation_name="",
|
|
1195
1199
|
method_name="list_prompts",
|
|
1196
1200
|
error_factory=lambda _: None,
|
|
@@ -1264,7 +1268,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1264
1268
|
# Fetch from server
|
|
1265
1269
|
result = await self._execute_on_server(
|
|
1266
1270
|
server_name=server_name,
|
|
1267
|
-
operation_type="prompts
|
|
1271
|
+
operation_type="prompts/list",
|
|
1268
1272
|
operation_name="",
|
|
1269
1273
|
method_name="list_prompts",
|
|
1270
1274
|
error_factory=lambda _: None,
|
|
@@ -1303,7 +1307,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1303
1307
|
try:
|
|
1304
1308
|
result = await self._execute_on_server(
|
|
1305
1309
|
server_name=s_name,
|
|
1306
|
-
operation_type="prompts
|
|
1310
|
+
operation_type="prompts/list",
|
|
1307
1311
|
operation_name="",
|
|
1308
1312
|
method_name="list_prompts",
|
|
1309
1313
|
error_factory=lambda _: None,
|
|
@@ -1358,22 +1362,15 @@ class MCPAggregator(ContextDependent):
|
|
|
1358
1362
|
|
|
1359
1363
|
async with self._refresh_lock:
|
|
1360
1364
|
try:
|
|
1361
|
-
# Fetch new tools from the server
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
async with gen_client(
|
|
1371
|
-
server_name,
|
|
1372
|
-
server_registry=self.context.server_registry,
|
|
1373
|
-
client_session_factory=self._create_session_factory(server_name),
|
|
1374
|
-
) as client:
|
|
1375
|
-
tools_result = await client.list_tools()
|
|
1376
|
-
new_tools = tools_result.tools or []
|
|
1365
|
+
# Fetch new tools from the server using _execute_on_server to properly record stats
|
|
1366
|
+
tools_result = await self._execute_on_server(
|
|
1367
|
+
server_name=server_name,
|
|
1368
|
+
operation_type="tools/list",
|
|
1369
|
+
operation_name="",
|
|
1370
|
+
method_name="list_tools",
|
|
1371
|
+
method_args={},
|
|
1372
|
+
)
|
|
1373
|
+
new_tools = tools_result.tools or []
|
|
1377
1374
|
|
|
1378
1375
|
# Update tool maps
|
|
1379
1376
|
async with self._tool_map_lock:
|
|
@@ -1489,7 +1486,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1489
1486
|
# Use the _execute_on_server method to call read_resource on the server
|
|
1490
1487
|
result = await self._execute_on_server(
|
|
1491
1488
|
server_name=server_name,
|
|
1492
|
-
operation_type="
|
|
1489
|
+
operation_type="resources/read",
|
|
1493
1490
|
operation_name=resource_uri,
|
|
1494
1491
|
method_name="read_resource",
|
|
1495
1492
|
method_args={"uri": uri},
|
|
@@ -1540,7 +1537,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1540
1537
|
# Use the _execute_on_server method to call list_resources on the server
|
|
1541
1538
|
result = await self._execute_on_server(
|
|
1542
1539
|
server_name=s_name,
|
|
1543
|
-
operation_type="resources
|
|
1540
|
+
operation_type="resources/list",
|
|
1544
1541
|
operation_name="",
|
|
1545
1542
|
method_name="list_resources",
|
|
1546
1543
|
method_args={}, # Empty dictionary instead of None
|
|
@@ -1593,7 +1590,7 @@ class MCPAggregator(ContextDependent):
|
|
|
1593
1590
|
# Use the _execute_on_server method to call list_tools on the server
|
|
1594
1591
|
result = await self._execute_on_server(
|
|
1595
1592
|
server_name=s_name,
|
|
1596
|
-
operation_type="tools
|
|
1593
|
+
operation_type="tools/list",
|
|
1597
1594
|
operation_name="",
|
|
1598
1595
|
method_name="list_tools",
|
|
1599
1596
|
method_args={},
|
fast_agent/mcp/sampling.py
CHANGED
|
@@ -77,6 +77,19 @@ async def sample(mcp_ctx: ClientSession, params: CreateMessageRequestParams) ->
|
|
|
77
77
|
Returns:
|
|
78
78
|
A CreateMessageResult containing the LLM's response
|
|
79
79
|
"""
|
|
80
|
+
# Get server name for notification tracking
|
|
81
|
+
server_name = "unknown"
|
|
82
|
+
if hasattr(mcp_ctx, "session") and hasattr(mcp_ctx.session, "session_server_name"):
|
|
83
|
+
server_name = mcp_ctx.session.session_server_name or "unknown"
|
|
84
|
+
|
|
85
|
+
# Start tracking sampling operation
|
|
86
|
+
try:
|
|
87
|
+
from fast_agent.ui import notification_tracker
|
|
88
|
+
notification_tracker.start_sampling(server_name)
|
|
89
|
+
except Exception:
|
|
90
|
+
# Don't let notification tracking break sampling
|
|
91
|
+
pass
|
|
92
|
+
|
|
80
93
|
model: str | None = None
|
|
81
94
|
api_key: str | None = None
|
|
82
95
|
try:
|
|
@@ -157,6 +170,14 @@ async def sample(mcp_ctx: ClientSession, params: CreateMessageRequestParams) ->
|
|
|
157
170
|
return SamplingConverter.error_result(
|
|
158
171
|
error_message=f"Error in sampling: {str(e)}", model=model
|
|
159
172
|
)
|
|
173
|
+
finally:
|
|
174
|
+
# End tracking sampling operation
|
|
175
|
+
try:
|
|
176
|
+
from fast_agent.ui import notification_tracker
|
|
177
|
+
notification_tracker.end_sampling(server_name)
|
|
178
|
+
except Exception:
|
|
179
|
+
# Don't let notification tracking break sampling
|
|
180
|
+
pass
|
|
160
181
|
|
|
161
182
|
|
|
162
183
|
def sampling_agent_config(
|
|
@@ -243,7 +243,9 @@ class TransportChannelMetrics:
|
|
|
243
243
|
self._get_last_error = event.detail
|
|
244
244
|
self._get_last_event = "error"
|
|
245
245
|
self._get_last_event_at = now
|
|
246
|
-
|
|
246
|
+
# Record 405 as "disabled" in timeline, not "error"
|
|
247
|
+
timeline_state = "disabled" if event.status_code == 405 else "error"
|
|
248
|
+
self._record_history("get", timeline_state, now)
|
|
247
249
|
|
|
248
250
|
def _handle_resumption_event(self, event: ChannelEvent, now: datetime) -> None:
|
|
249
251
|
if event.event_type == "message" and event.message is not None:
|
fast_agent/ui/console_display.py
CHANGED
|
@@ -623,33 +623,38 @@ class ConsoleDisplay:
|
|
|
623
623
|
if not self.config or not self.config.logger.show_tools:
|
|
624
624
|
return
|
|
625
625
|
|
|
626
|
-
#
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
630
|
-
)
|
|
631
|
-
else:
|
|
632
|
-
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
626
|
+
# Check if prompt_toolkit is active
|
|
627
|
+
try:
|
|
628
|
+
from prompt_toolkit.application.current import get_app
|
|
633
629
|
|
|
634
|
-
|
|
635
|
-
|
|
630
|
+
app = get_app()
|
|
631
|
+
# We're in interactive mode - add to notification tracker
|
|
632
|
+
from fast_agent.ui import notification_tracker
|
|
636
633
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
console.console.print(message, style="dim", markup=self._markup)
|
|
634
|
+
notification_tracker.add_tool_update(updated_server)
|
|
635
|
+
app.invalidate() # Force toolbar redraw
|
|
640
636
|
|
|
641
|
-
#
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
637
|
+
except: # noqa: E722
|
|
638
|
+
# No active prompt_toolkit session - display with rich as before
|
|
639
|
+
# Combined separator and status line
|
|
640
|
+
if agent_name:
|
|
641
|
+
left = (
|
|
642
|
+
f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
643
|
+
)
|
|
644
|
+
else:
|
|
645
|
+
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
645
646
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
from prompt_toolkit.application.current import get_app
|
|
647
|
+
right = f"[dim]{updated_server}[/dim]"
|
|
648
|
+
self._create_combined_separator_status(left, right)
|
|
649
649
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
650
|
+
# Display update message
|
|
651
|
+
message = f"Updating tools for server {updated_server}"
|
|
652
|
+
console.console.print(message, style="dim", markup=self._markup)
|
|
653
|
+
|
|
654
|
+
# Bottom separator
|
|
655
|
+
console.console.print()
|
|
656
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
657
|
+
console.console.print()
|
|
653
658
|
|
|
654
659
|
def _create_combined_separator_status(self, left_content: str, right_info: str = "") -> None:
|
|
655
660
|
"""
|
|
@@ -7,16 +7,16 @@ ELICITATION_STYLE = Style.from_dict(
|
|
|
7
7
|
{
|
|
8
8
|
# Dialog structure - use ansidefault for true black, remove problematic shadow
|
|
9
9
|
"dialog": "bg:ansidefault", # True black dialog using ansidefault
|
|
10
|
-
"dialog.body": "bg:ansidefault fg:
|
|
10
|
+
"dialog.body": "bg:ansidefault fg:ansidefault", # True black dialog body with default text color
|
|
11
11
|
"dialog shadow": "bg:ansidefault", # Set shadow background to match application
|
|
12
12
|
"dialog.border": "bg:ansidefault", # True black border background
|
|
13
13
|
# Set application background to true black
|
|
14
14
|
"application": "bg:ansidefault", # True black application background
|
|
15
15
|
# Title styling with better contrast
|
|
16
|
-
"title": "fg:
|
|
16
|
+
"title": "fg:ansidefault bold", # Default color title for terminal compatibility
|
|
17
17
|
# Buttons - only define focused state to preserve focus highlighting
|
|
18
18
|
"button.focused": "bg:ansibrightgreen fg:ansiblack bold", # Bright green with black text for contrast
|
|
19
|
-
"button.arrow": "fg:
|
|
19
|
+
"button.arrow": "fg:ansidefault bold", # Default color arrows for terminal compatibility
|
|
20
20
|
# Form elements with consistent green/yellow theme
|
|
21
21
|
# Checkboxes - green when checked, yellow when focused
|
|
22
22
|
"checkbox": "fg:ansidefault", # Default color unchecked checkbox (dimmer)
|
|
@@ -34,15 +34,15 @@ ELICITATION_STYLE = Style.from_dict(
|
|
|
34
34
|
# Frame styling with ANSI colors - make borders visible
|
|
35
35
|
"frame.border": "fg:ansibrightblack", # Bright black borders for subtlety
|
|
36
36
|
"frame.label": "fg:ansigray", # Gray frame labels (less prominent)
|
|
37
|
-
# Labels and text - use
|
|
38
|
-
"label": "fg:
|
|
37
|
+
# Labels and text - use default color for terminal compatibility
|
|
38
|
+
"label": "fg:ansidefault", # Default color labels for terminal compatibility
|
|
39
39
|
"message": "fg:ansibrightcyan", # Bright cyan messages (no bold)
|
|
40
40
|
# Agent and server names - make them match
|
|
41
41
|
"agent-name": "fg:ansibrightblue bold",
|
|
42
42
|
"server-name": "fg:ansibrightblue bold", # Same color as agent
|
|
43
43
|
# Validation errors - better contrast
|
|
44
|
-
"validation-toolbar": "bg:ansibrightred fg:
|
|
45
|
-
"validation-toolbar.text": "bg:ansibrightred fg:
|
|
44
|
+
"validation-toolbar": "bg:ansibrightred fg:ansidefault bold",
|
|
45
|
+
"validation-toolbar.text": "bg:ansibrightred fg:ansidefault",
|
|
46
46
|
"validation.border": "fg:ansibrightred",
|
|
47
47
|
"validation-error": "fg:ansibrightred bold", # For status line errors
|
|
48
48
|
# Separator styling
|
fast_agent/ui/enhanced_prompt.py
CHANGED
|
@@ -717,17 +717,33 @@ async def get_enhanced_input(
|
|
|
717
717
|
# Version/app label in green (dynamic version)
|
|
718
718
|
version_segment = f"fast-agent {app_version}"
|
|
719
719
|
|
|
720
|
+
# Add notifications - prioritize active events over completed ones
|
|
721
|
+
from fast_agent.ui import notification_tracker
|
|
722
|
+
|
|
723
|
+
notification_segment = ""
|
|
724
|
+
|
|
725
|
+
# Check for active events first (highest priority)
|
|
726
|
+
active_status = notification_tracker.get_active_status()
|
|
727
|
+
if active_status:
|
|
728
|
+
event_type = active_status['type'].upper()
|
|
729
|
+
server = active_status['server']
|
|
730
|
+
notification_segment = f" | <style fg='ansired' bg='ansiblack'>◀ {event_type} ({server})</style>"
|
|
731
|
+
elif notification_tracker.get_count() > 0:
|
|
732
|
+
# Show completed events summary when no active events
|
|
733
|
+
summary = notification_tracker.get_summary()
|
|
734
|
+
notification_segment = f" | ◀ {notification_tracker.get_count()} updates ({summary})"
|
|
735
|
+
|
|
720
736
|
if middle:
|
|
721
737
|
return HTML(
|
|
722
738
|
f" <style fg='{toolbar_color}' bg='ansiblack'> {agent_name} </style> "
|
|
723
739
|
f" {middle} | <style fg='{mode_style}' bg='ansiblack'> {mode_text} </style> | "
|
|
724
|
-
f"{version_segment}"
|
|
740
|
+
f"{version_segment}{notification_segment}"
|
|
725
741
|
)
|
|
726
742
|
else:
|
|
727
743
|
return HTML(
|
|
728
744
|
f" <style fg='{toolbar_color}' bg='ansiblack'> {agent_name} </style> "
|
|
729
745
|
f"Mode: <style fg='{mode_style}' bg='ansiblack'> {mode_text} </style> | "
|
|
730
|
-
f"{version_segment}"
|
|
746
|
+
f"{version_segment}{notification_segment}"
|
|
731
747
|
)
|
|
732
748
|
|
|
733
749
|
# A more terminal-agnostic style that should work across themes
|
|
@@ -766,7 +782,7 @@ async def get_enhanced_input(
|
|
|
766
782
|
session.app.key_bindings = bindings
|
|
767
783
|
|
|
768
784
|
# Create formatted prompt text
|
|
769
|
-
prompt_text = f"<ansibrightblue>{agent_name}</ansibrightblue>
|
|
785
|
+
prompt_text = f"<ansibrightblue>{agent_name}</ansibrightblue> ❯ "
|
|
770
786
|
|
|
771
787
|
# Add default value display if requested
|
|
772
788
|
if show_default and default and default != "STOP":
|
fast_agent/ui/mcp_display.py
CHANGED
|
@@ -14,6 +14,67 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from fast_agent.mcp.transport_tracking import ChannelSnapshot
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# Centralized color configuration
|
|
18
|
+
class Colours:
|
|
19
|
+
"""Color constants for MCP status display elements."""
|
|
20
|
+
|
|
21
|
+
# Timeline activity colors (Option A: Mixed Intensity)
|
|
22
|
+
ERROR = "bright_red" # Keep error bright
|
|
23
|
+
DISABLED = "bright_blue" # Keep disabled bright
|
|
24
|
+
RESPONSE = "blue" # Normal blue instead of bright
|
|
25
|
+
REQUEST = "yellow" # Normal yellow instead of bright
|
|
26
|
+
NOTIFICATION = "cyan" # Normal cyan instead of bright
|
|
27
|
+
PING = "dim green" # Keep ping dim
|
|
28
|
+
IDLE = "white dim"
|
|
29
|
+
NONE = "dim"
|
|
30
|
+
|
|
31
|
+
# Channel arrow states
|
|
32
|
+
ARROW_ERROR = "bright_red"
|
|
33
|
+
ARROW_DISABLED = "bright_yellow" # For explicitly disabled/off
|
|
34
|
+
ARROW_METHOD_NOT_ALLOWED = "cyan" # For 405 method not allowed (notification color)
|
|
35
|
+
ARROW_OFF = "black dim"
|
|
36
|
+
ARROW_IDLE = "bright_cyan" # Connected but no activity
|
|
37
|
+
ARROW_ACTIVE = "bright_green" # Connected with activity
|
|
38
|
+
|
|
39
|
+
# Capability token states
|
|
40
|
+
TOKEN_ERROR = "bright_red"
|
|
41
|
+
TOKEN_WARNING = "bright_cyan"
|
|
42
|
+
TOKEN_DISABLED = "dim"
|
|
43
|
+
TOKEN_HIGHLIGHTED = "bright_yellow"
|
|
44
|
+
TOKEN_ENABLED = "bright_green"
|
|
45
|
+
|
|
46
|
+
# Text elements
|
|
47
|
+
TEXT_DIM = "dim"
|
|
48
|
+
TEXT_DEFAULT = "default" # Use terminal's default text color
|
|
49
|
+
TEXT_BRIGHT = "bright_white"
|
|
50
|
+
TEXT_ERROR = "bright_red"
|
|
51
|
+
TEXT_WARNING = "bright_yellow"
|
|
52
|
+
TEXT_SUCCESS = "bright_green"
|
|
53
|
+
TEXT_INFO = "bright_blue"
|
|
54
|
+
TEXT_CYAN = "cyan"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Color mappings for different contexts
|
|
58
|
+
TIMELINE_COLORS = {
|
|
59
|
+
"error": Colours.ERROR,
|
|
60
|
+
"disabled": Colours.DISABLED,
|
|
61
|
+
"response": Colours.RESPONSE,
|
|
62
|
+
"request": Colours.REQUEST,
|
|
63
|
+
"notification": Colours.NOTIFICATION,
|
|
64
|
+
"ping": Colours.PING,
|
|
65
|
+
"none": Colours.IDLE,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
TIMELINE_COLORS_STDIO = {
|
|
69
|
+
"error": Colours.ERROR,
|
|
70
|
+
"request": Colours.TOKEN_ENABLED, # All activity shows as bright green
|
|
71
|
+
"response": Colours.TOKEN_ENABLED,
|
|
72
|
+
"notification": Colours.TOKEN_ENABLED,
|
|
73
|
+
"ping": Colours.PING,
|
|
74
|
+
"none": Colours.IDLE,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
17
78
|
def _format_compact_duration(seconds: float | None) -> str | None:
|
|
18
79
|
if seconds is None:
|
|
19
80
|
return None
|
|
@@ -58,7 +119,7 @@ def _format_session_id(session_id: str | None) -> Text:
|
|
|
58
119
|
|
|
59
120
|
|
|
60
121
|
def _build_aligned_field(
|
|
61
|
-
label: str, value: Text | str, *, label_width: int = 9, value_style: str =
|
|
122
|
+
label: str, value: Text | str, *, label_width: int = 9, value_style: str = Colours.TEXT_DEFAULT
|
|
62
123
|
) -> Text:
|
|
63
124
|
field = Text()
|
|
64
125
|
field.append(f"{label:<{label_width}}: ", style="dim")
|
|
@@ -134,7 +195,7 @@ def _format_capability_shorthand(
|
|
|
134
195
|
entries.append(("Ro", False, False))
|
|
135
196
|
|
|
136
197
|
mode = (status.elicitation_mode or "").lower()
|
|
137
|
-
if mode == "
|
|
198
|
+
if mode == "auto-cancel":
|
|
138
199
|
entries.append(("El", "red", False))
|
|
139
200
|
elif mode and mode != "none":
|
|
140
201
|
entries.append(("El", True, False))
|
|
@@ -153,14 +214,14 @@ def _format_capability_shorthand(
|
|
|
153
214
|
|
|
154
215
|
def token_style(supported, highlighted) -> str:
|
|
155
216
|
if supported == "red":
|
|
156
|
-
return
|
|
217
|
+
return Colours.TOKEN_ERROR
|
|
157
218
|
if supported == "blue":
|
|
158
|
-
return
|
|
219
|
+
return Colours.TOKEN_WARNING
|
|
159
220
|
if not supported:
|
|
160
|
-
return
|
|
221
|
+
return Colours.TOKEN_DISABLED
|
|
161
222
|
if highlighted:
|
|
162
|
-
return
|
|
163
|
-
return
|
|
223
|
+
return Colours.TOKEN_HIGHLIGHTED
|
|
224
|
+
return Colours.TOKEN_ENABLED
|
|
164
225
|
|
|
165
226
|
tokens = [
|
|
166
227
|
(label, token_style(supported, highlighted)) for label, supported, highlighted in entries
|
|
@@ -198,19 +259,16 @@ def _format_label(label: str, width: int = 10) -> str:
|
|
|
198
259
|
|
|
199
260
|
def _build_inline_timeline(buckets: Iterable[str]) -> str:
|
|
200
261
|
"""Build a compact timeline string for inline display."""
|
|
201
|
-
color_map = {
|
|
202
|
-
"error": "bright_red",
|
|
203
|
-
"disabled": "bright_blue",
|
|
204
|
-
"response": "bright_blue",
|
|
205
|
-
"request": "bright_yellow",
|
|
206
|
-
"notification": "bright_cyan",
|
|
207
|
-
"ping": "bright_green",
|
|
208
|
-
"none": "dim",
|
|
209
|
-
}
|
|
210
262
|
timeline = " [dim]10m[/dim] "
|
|
211
263
|
for state in buckets:
|
|
212
|
-
color =
|
|
213
|
-
|
|
264
|
+
color = TIMELINE_COLORS.get(state, Colours.NONE)
|
|
265
|
+
if state in {"idle", "none"}:
|
|
266
|
+
symbol = "·"
|
|
267
|
+
elif state == "request":
|
|
268
|
+
symbol = "◆" # Diamond for requests - rare and important
|
|
269
|
+
else:
|
|
270
|
+
symbol = "●" # Circle for other activity
|
|
271
|
+
timeline += f"[bold {color}]{symbol}[/bold {color}]"
|
|
214
272
|
timeline += " [dim]now[/dim]"
|
|
215
273
|
return timeline
|
|
216
274
|
|
|
@@ -298,45 +356,25 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
298
356
|
# Collect any errors to show at bottom
|
|
299
357
|
errors = []
|
|
300
358
|
|
|
301
|
-
#
|
|
302
|
-
if is_stdio
|
|
303
|
-
# Simplified color map for stdio: bright green for activity, dim for idle
|
|
304
|
-
timeline_color_map = {
|
|
305
|
-
"error": "bright_red", # Keep error as red
|
|
306
|
-
"request": "bright_green", # All activity shows as bright green
|
|
307
|
-
"response": "bright_green", # (not used in stdio but just in case)
|
|
308
|
-
"notification": "bright_green", # (not used in stdio but just in case)
|
|
309
|
-
"ping": "bright_green", # (not used in stdio but just in case)
|
|
310
|
-
"none": "white dim",
|
|
311
|
-
}
|
|
312
|
-
else:
|
|
313
|
-
# Full color map for HTTP channels
|
|
314
|
-
timeline_color_map = {
|
|
315
|
-
"error": "bright_red",
|
|
316
|
-
"disabled": "bright_blue",
|
|
317
|
-
"response": "bright_blue",
|
|
318
|
-
"request": "bright_yellow",
|
|
319
|
-
"notification": "bright_cyan",
|
|
320
|
-
"ping": "bright_green",
|
|
321
|
-
"none": "white dim",
|
|
322
|
-
}
|
|
359
|
+
# Get appropriate timeline color map
|
|
360
|
+
timeline_color_map = TIMELINE_COLORS_STDIO if is_stdio else TIMELINE_COLORS
|
|
323
361
|
|
|
324
362
|
for label, arrow, channel in entries:
|
|
325
363
|
line = Text(indent)
|
|
326
364
|
line.append("│ ", style="dim")
|
|
327
365
|
|
|
328
366
|
# Determine arrow color based on state
|
|
329
|
-
arrow_style =
|
|
367
|
+
arrow_style = Colours.ARROW_OFF # default no channel
|
|
330
368
|
if channel:
|
|
331
369
|
state = (channel.state or "open").lower()
|
|
332
370
|
|
|
333
|
-
# Check for 405 status code (method not allowed =
|
|
371
|
+
# Check for 405 status code (method not allowed = not an error, just unsupported)
|
|
334
372
|
if channel.last_status_code == 405:
|
|
335
|
-
arrow_style =
|
|
336
|
-
# Don't add 405 to errors list - it's
|
|
373
|
+
arrow_style = Colours.ARROW_METHOD_NOT_ALLOWED
|
|
374
|
+
# Don't add 405 to errors list - it's not an error, just method not supported
|
|
337
375
|
# Error state (non-405 errors)
|
|
338
376
|
elif state == "error":
|
|
339
|
-
arrow_style =
|
|
377
|
+
arrow_style = Colours.ARROW_ERROR
|
|
340
378
|
if channel.last_error and channel.last_status_code != 405:
|
|
341
379
|
error_msg = channel.last_error
|
|
342
380
|
if channel.last_status_code:
|
|
@@ -347,20 +385,41 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
347
385
|
errors.append((label.split()[0], error_msg))
|
|
348
386
|
# Explicitly disabled or off
|
|
349
387
|
elif state in {"off", "disabled"}:
|
|
350
|
-
arrow_style =
|
|
388
|
+
arrow_style = Colours.ARROW_OFF
|
|
351
389
|
# No activity (idle)
|
|
352
390
|
elif channel.request_count == 0 and channel.response_count == 0:
|
|
353
|
-
arrow_style =
|
|
391
|
+
arrow_style = Colours.ARROW_IDLE
|
|
354
392
|
# Active/connected with activity
|
|
355
393
|
elif state in {"open", "connected"}:
|
|
356
|
-
arrow_style =
|
|
394
|
+
arrow_style = Colours.ARROW_ACTIVE
|
|
357
395
|
# Fallback for other states
|
|
358
396
|
else:
|
|
359
|
-
arrow_style =
|
|
397
|
+
arrow_style = Colours.ARROW_IDLE
|
|
360
398
|
|
|
361
399
|
# Arrow and label with better spacing
|
|
362
|
-
|
|
363
|
-
|
|
400
|
+
# Use hollow arrow for 405 Method Not Allowed
|
|
401
|
+
if channel and channel.last_status_code == 405:
|
|
402
|
+
# Convert solid arrows to hollow for 405
|
|
403
|
+
hollow_arrows = {"◀": "◁", "▶": "▷", "⇄": "⇄"} # bidirectional stays same
|
|
404
|
+
display_arrow = hollow_arrows.get(arrow, arrow)
|
|
405
|
+
else:
|
|
406
|
+
display_arrow = arrow
|
|
407
|
+
line.append(display_arrow, style=arrow_style)
|
|
408
|
+
|
|
409
|
+
# Determine label style based on activity and special cases
|
|
410
|
+
if not channel:
|
|
411
|
+
# No channel = dim
|
|
412
|
+
label_style = Colours.TEXT_DIM
|
|
413
|
+
elif channel.last_status_code == 405 and "GET" in label:
|
|
414
|
+
# Special case: GET (SSE) with 405 = dim (hollow arrow already handled above)
|
|
415
|
+
label_style = Colours.TEXT_DIM
|
|
416
|
+
elif channel.request_count == 0 and channel.response_count == 0:
|
|
417
|
+
# No activity = dim
|
|
418
|
+
label_style = Colours.TEXT_DIM
|
|
419
|
+
else:
|
|
420
|
+
# Has activity = normal
|
|
421
|
+
label_style = Colours.TEXT_DEFAULT
|
|
422
|
+
line.append(f" {label:<13}", style=label_style)
|
|
364
423
|
|
|
365
424
|
# Always show timeline (dim black dots if no data)
|
|
366
425
|
line.append("10m ", style="dim")
|
|
@@ -368,11 +427,20 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
368
427
|
# Show actual activity
|
|
369
428
|
for bucket_state in channel.activity_buckets:
|
|
370
429
|
color = timeline_color_map.get(bucket_state, "dim")
|
|
371
|
-
|
|
430
|
+
if bucket_state in {"idle", "none"}:
|
|
431
|
+
symbol = "·"
|
|
432
|
+
elif is_stdio:
|
|
433
|
+
# For STDIO, all activity shows as filled circles since types are combined
|
|
434
|
+
symbol = "●"
|
|
435
|
+
elif bucket_state == "request":
|
|
436
|
+
symbol = "◆" # Diamond for requests - rare and important
|
|
437
|
+
else:
|
|
438
|
+
symbol = "●" # Circle for other activity
|
|
439
|
+
line.append(symbol, style=f"bold {color}")
|
|
372
440
|
else:
|
|
373
|
-
# Show dim
|
|
441
|
+
# Show dim dots for no activity
|
|
374
442
|
for _ in range(20):
|
|
375
|
-
line.append("
|
|
443
|
+
line.append("·", style="black dim")
|
|
376
444
|
line.append(" now", style="dim")
|
|
377
445
|
|
|
378
446
|
# Metrics - different layouts for stdio vs HTTP
|
|
@@ -380,26 +448,41 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
380
448
|
# Simplified activity column for stdio
|
|
381
449
|
if channel and channel.message_count > 0:
|
|
382
450
|
activity = str(channel.message_count).rjust(8)
|
|
383
|
-
activity_style =
|
|
451
|
+
activity_style = Colours.TEXT_DEFAULT
|
|
384
452
|
else:
|
|
385
453
|
activity = "-".rjust(8)
|
|
386
|
-
activity_style =
|
|
454
|
+
activity_style = Colours.TEXT_DIM
|
|
387
455
|
line.append(f" {activity}", style=activity_style)
|
|
388
456
|
else:
|
|
389
457
|
# Original HTTP columns
|
|
390
458
|
if channel:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
459
|
+
# Show "-" for shut/disabled channels (405, off, disabled states)
|
|
460
|
+
channel_state = (channel.state or "open").lower()
|
|
461
|
+
is_shut = (
|
|
462
|
+
channel.last_status_code == 405
|
|
463
|
+
or channel_state in {"off", "disabled"}
|
|
464
|
+
or (channel_state == "error" and channel.last_status_code == 405)
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
if is_shut:
|
|
468
|
+
req = "-".rjust(5)
|
|
469
|
+
resp = "-".rjust(5)
|
|
470
|
+
notif = "-".rjust(5)
|
|
471
|
+
ping = "-".rjust(5)
|
|
472
|
+
metrics_style = Colours.TEXT_DIM
|
|
473
|
+
else:
|
|
474
|
+
req = str(channel.request_count).rjust(5)
|
|
475
|
+
resp = str(channel.response_count).rjust(5)
|
|
476
|
+
notif = str(channel.notification_count).rjust(5)
|
|
477
|
+
ping = str(channel.ping_count if channel.ping_count else "-").rjust(5)
|
|
478
|
+
metrics_style = Colours.TEXT_DEFAULT
|
|
395
479
|
else:
|
|
396
480
|
req = "-".rjust(5)
|
|
397
481
|
resp = "-".rjust(5)
|
|
398
482
|
notif = "-".rjust(5)
|
|
399
483
|
ping = "-".rjust(5)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
)
|
|
484
|
+
metrics_style = Colours.TEXT_DIM
|
|
485
|
+
line.append(f" {req} {resp} {notif} {ping}", style=metrics_style)
|
|
403
486
|
|
|
404
487
|
console.console.print(line)
|
|
405
488
|
|
|
@@ -416,13 +499,13 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
416
499
|
|
|
417
500
|
for channel_type, error_msg in errors:
|
|
418
501
|
error_line = Text(indent)
|
|
419
|
-
error_line.append("│ ", style=
|
|
420
|
-
error_line.append("⚠ ", style=
|
|
421
|
-
error_line.append(f"{channel_type}: ", style=
|
|
502
|
+
error_line.append("│ ", style=Colours.TEXT_DIM)
|
|
503
|
+
error_line.append("⚠ ", style=Colours.TEXT_WARNING)
|
|
504
|
+
error_line.append(f"{channel_type}: ", style=Colours.TEXT_DEFAULT)
|
|
422
505
|
# Truncate long error messages
|
|
423
506
|
if len(error_msg) > 60:
|
|
424
507
|
error_msg = error_msg[:57] + "..."
|
|
425
|
-
error_line.append(error_msg, style=
|
|
508
|
+
error_line.append(error_msg, style=Colours.TEXT_ERROR)
|
|
426
509
|
console.console.print(error_line)
|
|
427
510
|
|
|
428
511
|
# Legend if any timelines shown
|
|
@@ -444,24 +527,30 @@ def _render_channel_summary(status: ServerStatus, indent: str, total_width: int)
|
|
|
444
527
|
if is_stdio:
|
|
445
528
|
# Simplified legend for stdio: just activity vs idle
|
|
446
529
|
legend_map = [
|
|
447
|
-
("activity", "
|
|
448
|
-
("idle",
|
|
530
|
+
("activity", f"bold {Colours.TOKEN_ENABLED}"),
|
|
531
|
+
("idle", Colours.IDLE),
|
|
449
532
|
]
|
|
450
533
|
else:
|
|
451
534
|
# Full legend for HTTP channels
|
|
452
535
|
legend_map = [
|
|
453
|
-
("error", "
|
|
454
|
-
("response", "
|
|
455
|
-
("request", "
|
|
456
|
-
("notification", "
|
|
457
|
-
("ping",
|
|
458
|
-
("idle",
|
|
536
|
+
("error", f"bold {Colours.ERROR}"),
|
|
537
|
+
("response", f"bold {Colours.RESPONSE}"),
|
|
538
|
+
("request", f"bold {Colours.REQUEST}"),
|
|
539
|
+
("notification", f"bold {Colours.NOTIFICATION}"),
|
|
540
|
+
("ping", Colours.PING),
|
|
541
|
+
("idle", Colours.IDLE),
|
|
459
542
|
]
|
|
460
543
|
|
|
461
544
|
for i, (name, color) in enumerate(legend_map):
|
|
462
545
|
if i > 0:
|
|
463
546
|
footer.append(" ", style="dim")
|
|
464
|
-
|
|
547
|
+
if name == "idle":
|
|
548
|
+
symbol = "·"
|
|
549
|
+
elif name == "request":
|
|
550
|
+
symbol = "◆" # Diamond for requests
|
|
551
|
+
else:
|
|
552
|
+
symbol = "●"
|
|
553
|
+
footer.append(symbol, style=f"{color}")
|
|
465
554
|
footer.append(f" {name}", style="dim")
|
|
466
555
|
|
|
467
556
|
console.console.print(footer)
|
|
@@ -526,10 +615,10 @@ async def render_mcp_status(agent, indent: str = "") -> None:
|
|
|
526
615
|
version_display = version_display[:9] + "..."
|
|
527
616
|
|
|
528
617
|
header_label = Text(indent)
|
|
529
|
-
header_label.append("▎", style=
|
|
530
|
-
header_label.append("●", style="dim
|
|
531
|
-
header_label.append(f" [{index:2}] ", style=
|
|
532
|
-
header_label.append(server, style="
|
|
618
|
+
header_label.append("▎", style=Colours.TEXT_CYAN)
|
|
619
|
+
header_label.append("●", style=f"dim {Colours.TEXT_CYAN}")
|
|
620
|
+
header_label.append(f" [{index:2}] ", style=Colours.TEXT_CYAN)
|
|
621
|
+
header_label.append(server, style=f"{Colours.TEXT_INFO} bold")
|
|
533
622
|
render_header(header_label)
|
|
534
623
|
|
|
535
624
|
# First line: name and version
|
|
@@ -571,22 +660,22 @@ async def render_mcp_status(agent, indent: str = "") -> None:
|
|
|
571
660
|
|
|
572
661
|
duration = _format_compact_duration(status.staleness_seconds)
|
|
573
662
|
if duration:
|
|
574
|
-
last_text = Text("last activity: ", style=
|
|
575
|
-
last_text.append(duration, style=
|
|
576
|
-
last_text.append(" ago", style=
|
|
663
|
+
last_text = Text("last activity: ", style=Colours.TEXT_DIM)
|
|
664
|
+
last_text.append(duration, style=Colours.TEXT_DEFAULT)
|
|
665
|
+
last_text.append(" ago", style=Colours.TEXT_DIM)
|
|
577
666
|
state_segments.append(last_text)
|
|
578
667
|
|
|
579
668
|
if status.error_message and status.is_connected is False:
|
|
580
|
-
state_segments.append(Text(status.error_message, style=
|
|
669
|
+
state_segments.append(Text(status.error_message, style=Colours.TEXT_ERROR))
|
|
581
670
|
|
|
582
671
|
instr_available = bool(status.instructions_available)
|
|
583
672
|
if instr_available and status.instructions_enabled is False:
|
|
584
|
-
state_segments.append(Text("instructions disabled", style=
|
|
673
|
+
state_segments.append(Text("instructions disabled", style=Colours.TEXT_ERROR))
|
|
585
674
|
elif instr_available and not template_expected:
|
|
586
|
-
state_segments.append(Text("template missing", style=
|
|
675
|
+
state_segments.append(Text("template missing", style=Colours.TEXT_WARNING))
|
|
587
676
|
|
|
588
677
|
if status.spoofing_enabled:
|
|
589
|
-
state_segments.append(Text("client spoof", style=
|
|
678
|
+
state_segments.append(Text("client spoof", style=Colours.TEXT_WARNING))
|
|
590
679
|
|
|
591
680
|
# Main status line (without transport and connected)
|
|
592
681
|
if state_segments:
|
|
@@ -601,8 +690,8 @@ async def render_mcp_status(agent, indent: str = "") -> None:
|
|
|
601
690
|
calls = _summarise_call_counts(status.call_counts)
|
|
602
691
|
if calls:
|
|
603
692
|
calls_line = Text(indent + " ")
|
|
604
|
-
calls_line.append("mcp calls: ", style=
|
|
605
|
-
calls_line.append(calls, style=
|
|
693
|
+
calls_line.append("mcp calls: ", style=Colours.TEXT_DIM)
|
|
694
|
+
calls_line.append(calls, style=Colours.TEXT_DEFAULT)
|
|
606
695
|
console.console.print(calls_line)
|
|
607
696
|
_render_channel_summary(status, indent, total_width)
|
|
608
697
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced notification tracker for prompt_toolkit toolbar display.
|
|
3
|
+
Tracks both active events (sampling/elicitation) and completed notifications.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
# Active events currently in progress
|
|
10
|
+
active_events: Dict[str, Dict[str, str]] = {}
|
|
11
|
+
|
|
12
|
+
# Completed notifications history
|
|
13
|
+
notifications: List[Dict[str, str]] = []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_tool_update(server_name: str) -> None:
|
|
17
|
+
"""Add a tool update notification.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
server_name: Name of the server that had tools updated
|
|
21
|
+
"""
|
|
22
|
+
notifications.append({
|
|
23
|
+
'type': 'tool_update',
|
|
24
|
+
'server': server_name
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def start_sampling(server_name: str) -> None:
|
|
29
|
+
"""Start tracking a sampling operation.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
server_name: Name of the server making the sampling request
|
|
33
|
+
"""
|
|
34
|
+
active_events['sampling'] = {
|
|
35
|
+
'server': server_name,
|
|
36
|
+
'start_time': datetime.now().isoformat()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Force prompt_toolkit to redraw if active
|
|
40
|
+
try:
|
|
41
|
+
from prompt_toolkit.application.current import get_app
|
|
42
|
+
get_app().invalidate()
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def end_sampling(server_name: str) -> None:
|
|
48
|
+
"""End tracking a sampling operation and add to completed notifications.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
server_name: Name of the server that made the sampling request
|
|
52
|
+
"""
|
|
53
|
+
if 'sampling' in active_events:
|
|
54
|
+
del active_events['sampling']
|
|
55
|
+
|
|
56
|
+
notifications.append({
|
|
57
|
+
'type': 'sampling',
|
|
58
|
+
'server': server_name
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
# Force prompt_toolkit to redraw if active
|
|
62
|
+
try:
|
|
63
|
+
from prompt_toolkit.application.current import get_app
|
|
64
|
+
get_app().invalidate()
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def start_elicitation(server_name: str) -> None:
|
|
70
|
+
"""Start tracking an elicitation operation.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
server_name: Name of the server making the elicitation request
|
|
74
|
+
"""
|
|
75
|
+
active_events['elicitation'] = {
|
|
76
|
+
'server': server_name,
|
|
77
|
+
'start_time': datetime.now().isoformat()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Force prompt_toolkit to redraw if active
|
|
81
|
+
try:
|
|
82
|
+
from prompt_toolkit.application.current import get_app
|
|
83
|
+
get_app().invalidate()
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def end_elicitation(server_name: str) -> None:
|
|
89
|
+
"""End tracking an elicitation operation and add to completed notifications.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
server_name: Name of the server that made the elicitation request
|
|
93
|
+
"""
|
|
94
|
+
if 'elicitation' in active_events:
|
|
95
|
+
del active_events['elicitation']
|
|
96
|
+
|
|
97
|
+
notifications.append({
|
|
98
|
+
'type': 'elicitation',
|
|
99
|
+
'server': server_name
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
# Force prompt_toolkit to redraw if active
|
|
103
|
+
try:
|
|
104
|
+
from prompt_toolkit.application.current import get_app
|
|
105
|
+
get_app().invalidate()
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_active_status() -> Optional[Dict[str, str]]:
|
|
111
|
+
"""Get currently active operation, if any.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dict with 'type' and 'server' keys, or None if nothing active
|
|
115
|
+
"""
|
|
116
|
+
if 'sampling' in active_events:
|
|
117
|
+
return {'type': 'sampling', 'server': active_events['sampling']['server']}
|
|
118
|
+
if 'elicitation' in active_events:
|
|
119
|
+
return {'type': 'elicitation', 'server': active_events['elicitation']['server']}
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def clear() -> None:
|
|
124
|
+
"""Clear all notifications and active events."""
|
|
125
|
+
notifications.clear()
|
|
126
|
+
active_events.clear()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_count() -> int:
|
|
130
|
+
"""Get the current completed notification count."""
|
|
131
|
+
return len(notifications)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_latest() -> Dict[str, str] | None:
|
|
135
|
+
"""Get the most recent completed notification."""
|
|
136
|
+
return notifications[-1] if notifications else None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_summary() -> str:
|
|
140
|
+
"""Get a summary of completed notifications by type.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
String like "3 tools, 1 sampling, 2 elicitations" or "1 tool update"
|
|
144
|
+
"""
|
|
145
|
+
if not notifications:
|
|
146
|
+
return ""
|
|
147
|
+
|
|
148
|
+
counts = {}
|
|
149
|
+
for notification in notifications:
|
|
150
|
+
event_type = notification['type']
|
|
151
|
+
if event_type == 'tool_update':
|
|
152
|
+
counts['tools'] = counts.get('tools', 0) + 1
|
|
153
|
+
else:
|
|
154
|
+
# For sampling/elicitation, use the type directly
|
|
155
|
+
counts[event_type] = counts.get(event_type, 0) + 1
|
|
156
|
+
|
|
157
|
+
# Build summary string
|
|
158
|
+
parts = []
|
|
159
|
+
for event_type, count in sorted(counts.items()):
|
|
160
|
+
if event_type == 'tools':
|
|
161
|
+
parts.append(f"{count} tool{'s' if count != 1 else ''}")
|
|
162
|
+
elif event_type == 'sampling':
|
|
163
|
+
parts.append(f"{count} sample{'s' if count != 1 else ''}")
|
|
164
|
+
else:
|
|
165
|
+
parts.append(f"{count} {event_type}{'s' if count != 1 else ''}")
|
|
166
|
+
|
|
167
|
+
return ", ".join(parts)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
fast_agent/__init__.py,sha256=ns6CPmjOL5y7cyV4XgFTfMGcfLnuBJwVTbcjJ5Co3x4,4152
|
|
2
|
-
fast_agent/config.py,sha256=
|
|
2
|
+
fast_agent/config.py,sha256=tKnAhGAADpuwU7DggScG0VESs7el4R1Y1T3MUPzD5_U,22509
|
|
3
3
|
fast_agent/constants.py,sha256=IoXL5m4L0iLlcRrKerMaK3ZPcS6KCJgK8b_bj1BAR60,345
|
|
4
4
|
fast_agent/context.py,sha256=nBelOqehSH91z3aG2nYhwETP-biRzz-iuA2fqmKdHP8,7700
|
|
5
5
|
fast_agent/context_dependent.py,sha256=KU1eydVBoIt4bYOZroqxDgE1AUexDaZi7hurE26QsF4,1584
|
|
@@ -54,7 +54,7 @@ fast_agent/core/logging/logger.py,sha256=L-hLfUGFCIABoNYDiUkNHWvFxL6j-6zn5Pc5E7a
|
|
|
54
54
|
fast_agent/core/logging/transport.py,sha256=i_WYXk5mqyfetT72bCYrbdrMWcuL1HJCyeQKfQg7U2w,16994
|
|
55
55
|
fast_agent/history/history_exporter.py,sha256=oqkw7qC5rrW73u20tkIqt8yBWPoVzCTC61x2Q2rOKGs,1404
|
|
56
56
|
fast_agent/human_input/__init__.py,sha256=4Jr_0JLJwdQ3iEUNd6Api9InldtnRMDv_WeZq_WEHpA,935
|
|
57
|
-
fast_agent/human_input/elicitation_handler.py,sha256=
|
|
57
|
+
fast_agent/human_input/elicitation_handler.py,sha256=diR8jWRCAuoqTDQHJ3C097EORcLFxpLjdz6EubVOnI8,4776
|
|
58
58
|
fast_agent/human_input/elicitation_state.py,sha256=L_vSTpw1-TSDumRYa89Me-twWRbUL2w7GNVhVJmn3KE,1152
|
|
59
59
|
fast_agent/human_input/form_fields.py,sha256=aE7HdR-wOPO_6HllNaJXtn3BzpPsC4TctUApbveRk8g,7644
|
|
60
60
|
fast_agent/human_input/simple_form.py,sha256=_flUll9z93VPjKqLS7gvz4L1_YJ3-KNx8_ZpUaaxhoQ,3508
|
|
@@ -97,14 +97,14 @@ fast_agent/llm/provider/openai/openai_multipart.py,sha256=uuoRMYWiznYksBODbknBuq
|
|
|
97
97
|
fast_agent/llm/provider/openai/openai_utils.py,sha256=RI9UgF7SGkZ02bAep7glvLy3erbismmZp7wXDsRoJPQ,2034
|
|
98
98
|
fast_agent/mcp/__init__.py,sha256=Q-86MBawKmB602VMut8U3uqUQDOrTAMx2HmHKnWT32Q,1371
|
|
99
99
|
fast_agent/mcp/common.py,sha256=MpSC0fLO21RcDz4VApah4C8_LisVGz7OXkR17Xw-9mY,431
|
|
100
|
-
fast_agent/mcp/elicitation_factory.py,sha256=
|
|
100
|
+
fast_agent/mcp/elicitation_factory.py,sha256=d4unxJyR0S-qH05yBZ_oQ08XLBToFtUzzFFYvv0wLrY,3172
|
|
101
101
|
fast_agent/mcp/elicitation_handlers.py,sha256=LWNKn850Ht9V1SGTfAZDptlsgzrycNhOPNsCcqzsuUY,6884
|
|
102
102
|
fast_agent/mcp/gen_client.py,sha256=Q0hhCVzC659GsvTLJIbhUBgGwsAJRL8b3ejTFokyjn4,3038
|
|
103
103
|
fast_agent/mcp/hf_auth.py,sha256=ndDvR7E9LCc5dBiMsStFXtvvX9lYrL-edCq_qJw4lDw,4476
|
|
104
104
|
fast_agent/mcp/interfaces.py,sha256=xCWONGXe4uQSmmBlMZRD3mflPegTJnz2caVNihEl3ok,2411
|
|
105
105
|
fast_agent/mcp/logger_textio.py,sha256=4YLVXlXghdGm1s_qp1VoAWEX_eWufBfD2iD7l08yoak,3170
|
|
106
106
|
fast_agent/mcp/mcp_agent_client_session.py,sha256=5_9cW3CSzYspRzb8lOJTFx9E8Qk7ieCBASBbUtNrmiY,15578
|
|
107
|
-
fast_agent/mcp/mcp_aggregator.py,sha256=
|
|
107
|
+
fast_agent/mcp/mcp_aggregator.py,sha256=HzNyKuUelAlp5JBkjMAaOB2JvdMgpm0ItEPGjidI9l0,67198
|
|
108
108
|
fast_agent/mcp/mcp_connection_manager.py,sha256=nbjcyWGK3LQhuFUABV1Mxf_v3rWFSMXNsDHV1h3qDUI,23803
|
|
109
109
|
fast_agent/mcp/mcp_content.py,sha256=F9bgJ57EO9sgWg1m-eTNM6xd9js79mHKf4e9O8K8jrI,8829
|
|
110
110
|
fast_agent/mcp/mime_utils.py,sha256=D6YXNdZJ351BjacSW5o0sVF_hrWuRHD6UyWS4TDlLZI,2915
|
|
@@ -114,10 +114,10 @@ fast_agent/mcp/prompt_message_extended.py,sha256=BsiV2SsiZkDlvqzvjeSowq8Ojvowr9X
|
|
|
114
114
|
fast_agent/mcp/prompt_render.py,sha256=AqDaQqM6kqciV9X79S5rsRr3VjcQ_2JOiLaHqpRzsS4,2888
|
|
115
115
|
fast_agent/mcp/prompt_serialization.py,sha256=QMbY0aa_UlJ7bbxl_muOm2TYeYbBVTEeEMHFmEy99ss,20182
|
|
116
116
|
fast_agent/mcp/resource_utils.py,sha256=cu-l9aOy-NFs8tPihYRNjsB2QSuime8KGOGpUvihp84,6589
|
|
117
|
-
fast_agent/mcp/sampling.py,sha256=
|
|
117
|
+
fast_agent/mcp/sampling.py,sha256=6S9bpGCFGC5azIGE-zxODvKgBbBn1x6amL5mc4sMg_4,7491
|
|
118
118
|
fast_agent/mcp/stdio_tracking_simple.py,sha256=T6kCIb6YjwqKtXHz_6HvlLLYiSCbuggt2xCXSihVnIg,1918
|
|
119
119
|
fast_agent/mcp/streamable_http_tracking.py,sha256=bcNNReokho6WMjWEH13F33bUSkjJ2F5l3qnegkDqdMA,11465
|
|
120
|
-
fast_agent/mcp/transport_tracking.py,sha256=
|
|
120
|
+
fast_agent/mcp/transport_tracking.py,sha256=tsc2Ntf47KbKXs8DzRkqvG0U-FbpwU2VxemNfRbJBpo,24088
|
|
121
121
|
fast_agent/mcp/ui_agent.py,sha256=OBGEuFpOPPK7EthPRwzxmtzu1SDIeZy-vHwdRsyDNQk,1424
|
|
122
122
|
fast_agent/mcp/ui_mixin.py,sha256=iOlSNJVPwiMUun0clCiWyot59Qgy8R7ZvUgH2afRnQA,7662
|
|
123
123
|
fast_agent/mcp/helpers/__init__.py,sha256=o6-HuX6bEVFnfT_wgclFOVb1NxtOsJEOnHX8L2IqDdw,857
|
|
@@ -191,19 +191,20 @@ fast_agent/types/__init__.py,sha256=y-53m-C4drf4Rx8Bbnk_GAhko9LdNYCyRUWya8e0mos,
|
|
|
191
191
|
fast_agent/types/llm_stop_reason.py,sha256=bWe97OfhALUe8uQeAQOnTdPlYzJiabIfo8u38kPgj3Q,2293
|
|
192
192
|
fast_agent/ui/__init__.py,sha256=MXxTQjFdF7mI_3JHxBPd-aoZYLlxV_-51-Trqgv5-3w,1104
|
|
193
193
|
fast_agent/ui/console.py,sha256=Gjf2QLFumwG1Lav__c07X_kZxxEUSkzV-1_-YbAwcwo,813
|
|
194
|
-
fast_agent/ui/console_display.py,sha256=
|
|
194
|
+
fast_agent/ui/console_display.py,sha256=oZg-SMd_F0_1azsxWF5ug7cee_-ou2xOgGeW_y_6xrI,42034
|
|
195
195
|
fast_agent/ui/elicitation_form.py,sha256=t3UhBG44YmxTLu1RjCnHwW36eQQaroE45CiBGJB2czg,29410
|
|
196
|
-
fast_agent/ui/elicitation_style.py,sha256
|
|
197
|
-
fast_agent/ui/enhanced_prompt.py,sha256=
|
|
196
|
+
fast_agent/ui/elicitation_style.py,sha256=-WqXgVjVs65oNwhCDw3E0A9cCyw95IOe6LYCJgjT6ok,3939
|
|
197
|
+
fast_agent/ui/enhanced_prompt.py,sha256=nGtYqvabfwYqCNczqWEf36AjY4laetG8CI__ePKtwKs,43496
|
|
198
198
|
fast_agent/ui/interactive_prompt.py,sha256=Y-tYaU_5jqjDLRRgP3rzoMAXmJxYHVIVPsd8gWkcQso,44823
|
|
199
|
-
fast_agent/ui/mcp_display.py,sha256=
|
|
199
|
+
fast_agent/ui/mcp_display.py,sha256=54NVlWL6KsvhLP2tZm3edC3RH7fiLOJeKOYQCJ55uks,27142
|
|
200
200
|
fast_agent/ui/mcp_ui_utils.py,sha256=hV7z-yHX86BgdH6CMmN5qyOUjyiegQXLJOa5n5A1vQs,8476
|
|
201
201
|
fast_agent/ui/mermaid_utils.py,sha256=MpcRyVCPMTwU1XeIxnyFg0fQLjcyXZduWRF8NhEqvXE,5332
|
|
202
|
+
fast_agent/ui/notification_tracker.py,sha256=XU2_C_1PwKEReyL2L6sFfFOq-3CczMlBiIIspxUjx0E,4625
|
|
202
203
|
fast_agent/ui/progress_display.py,sha256=hajDob65PttiJ2mPS6FsCtnmTcnyvDWGn-UqQboXqkQ,361
|
|
203
204
|
fast_agent/ui/rich_progress.py,sha256=fMiTigtkll4gL4Ik5WYHx-t0a92jfUovj0b580rT6J0,7735
|
|
204
205
|
fast_agent/ui/usage_display.py,sha256=ltJpn_sDzo8PDNSXWx-QdEUbQWUnhmajCItNt5mA5rM,7285
|
|
205
|
-
fast_agent_mcp-0.3.
|
|
206
|
-
fast_agent_mcp-0.3.
|
|
207
|
-
fast_agent_mcp-0.3.
|
|
208
|
-
fast_agent_mcp-0.3.
|
|
209
|
-
fast_agent_mcp-0.3.
|
|
206
|
+
fast_agent_mcp-0.3.11.dist-info/METADATA,sha256=hXINwYTIB7UUzPSbYOlPN0Mcq-YUKy1nWlfs0Gx5hcQ,31697
|
|
207
|
+
fast_agent_mcp-0.3.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
208
|
+
fast_agent_mcp-0.3.11.dist-info/entry_points.txt,sha256=i6Ujja9J-hRxttOKqTYdbYP_tyaS4gLHg53vupoCSsg,199
|
|
209
|
+
fast_agent_mcp-0.3.11.dist-info/licenses/LICENSE,sha256=Gx1L3axA4PnuK4FxsbX87jQ1opoOkSFfHHSytW6wLUU,10935
|
|
210
|
+
fast_agent_mcp-0.3.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|