fast-agent-mcp 0.3.5__py3-none-any.whl → 0.3.7__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.

Files changed (41) hide show
  1. fast_agent/__init__.py +9 -1
  2. fast_agent/agents/agent_types.py +11 -11
  3. fast_agent/agents/llm_agent.py +76 -40
  4. fast_agent/agents/llm_decorator.py +355 -6
  5. fast_agent/agents/mcp_agent.py +154 -59
  6. fast_agent/agents/tool_agent.py +60 -4
  7. fast_agent/agents/workflow/router_agent.py +10 -2
  8. fast_agent/cli/commands/auth.py +52 -29
  9. fast_agent/cli/commands/check_config.py +26 -5
  10. fast_agent/cli/commands/go.py +11 -5
  11. fast_agent/cli/commands/setup.py +4 -7
  12. fast_agent/config.py +4 -1
  13. fast_agent/constants.py +2 -0
  14. fast_agent/core/agent_app.py +2 -0
  15. fast_agent/core/direct_factory.py +39 -120
  16. fast_agent/core/fastagent.py +2 -2
  17. fast_agent/history/history_exporter.py +3 -3
  18. fast_agent/llm/fastagent_llm.py +3 -3
  19. fast_agent/llm/provider/openai/llm_openai.py +57 -8
  20. fast_agent/mcp/__init__.py +1 -2
  21. fast_agent/mcp/mcp_aggregator.py +34 -1
  22. fast_agent/mcp/mcp_connection_manager.py +23 -4
  23. fast_agent/mcp/oauth_client.py +32 -4
  24. fast_agent/mcp/prompt_message_extended.py +2 -0
  25. fast_agent/mcp/prompt_serialization.py +124 -39
  26. fast_agent/mcp/prompts/prompt_load.py +34 -32
  27. fast_agent/mcp/prompts/prompt_server.py +26 -11
  28. fast_agent/resources/setup/.gitignore +6 -0
  29. fast_agent/resources/setup/agent.py +8 -1
  30. fast_agent/resources/setup/fastagent.config.yaml +2 -2
  31. fast_agent/resources/setup/pyproject.toml.tmpl +6 -0
  32. fast_agent/types/__init__.py +3 -1
  33. fast_agent/ui/console_display.py +48 -31
  34. fast_agent/ui/enhanced_prompt.py +119 -64
  35. fast_agent/ui/interactive_prompt.py +66 -40
  36. fast_agent/ui/rich_progress.py +12 -8
  37. {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.7.dist-info}/METADATA +3 -3
  38. {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.7.dist-info}/RECORD +41 -41
  39. {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.7.dist-info}/WHEEL +0 -0
  40. {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.7.dist-info}/entry_points.txt +0 -0
  41. {fast_agent_mcp-0.3.5.dist-info → fast_agent_mcp-0.3.7.dist-info}/licenses/LICENSE +0 -0
@@ -26,6 +26,7 @@ class MessageType(Enum):
26
26
 
27
27
  USER = "user"
28
28
  ASSISTANT = "assistant"
29
+ SYSTEM = "system"
29
30
  TOOL_CALL = "tool_call"
30
31
  TOOL_RESULT = "tool_result"
31
32
 
@@ -44,6 +45,12 @@ MESSAGE_CONFIGS = {
44
45
  "arrow_style": "dim green",
45
46
  "highlight_color": "bright_green",
46
47
  },
48
+ MessageType.SYSTEM: {
49
+ "block_color": "yellow",
50
+ "arrow": "●",
51
+ "arrow_style": "dim yellow",
52
+ "highlight_color": "bright_yellow",
53
+ },
47
54
  MessageType.TOOL_CALL: {
48
55
  "block_color": "magenta",
49
56
  "arrow": "◀",
@@ -144,7 +151,7 @@ class ConsoleDisplay:
144
151
  name: str | None = None,
145
152
  right_info: str = "",
146
153
  bottom_metadata: List[str] | None = None,
147
- highlight_items: str | List[str] | None = None,
154
+ highlight_index: int | None = None,
148
155
  max_item_length: int | None = None,
149
156
  is_error: bool = False,
150
157
  truncate_content: bool = True,
@@ -159,7 +166,7 @@ class ConsoleDisplay:
159
166
  name: Optional name to display (agent name, user name, etc.)
160
167
  right_info: Information to display on the right side of the header
161
168
  bottom_metadata: Optional list of items for bottom separator
162
- highlight_items: Item(s) to highlight in bottom metadata
169
+ highlight_index: Index of item to highlight in bottom metadata (0-based), or None
163
170
  max_item_length: Optional max length for bottom metadata items (with ellipsis)
164
171
  is_error: For tool results, whether this is an error (uses red color)
165
172
  truncate_content: Whether to truncate long content
@@ -199,16 +206,6 @@ class ConsoleDisplay:
199
206
  if max_item_length:
200
207
  display_items = self._shorten_items(bottom_metadata, max_item_length)
201
208
 
202
- # Normalize highlight_items
203
- if highlight_items is None:
204
- highlight_items = []
205
- elif isinstance(highlight_items, str):
206
- highlight_items = [highlight_items]
207
-
208
- # Shorten highlight items to match if we shortened display items
209
- if max_item_length:
210
- highlight_items = self._shorten_items(highlight_items, max_item_length)
211
-
212
209
  # Format the metadata with highlighting, clipped to available width
213
210
  # Compute available width for the metadata segment (excluding the fixed prefix/suffix)
214
211
  total_width = console.console.size.width
@@ -220,7 +217,7 @@ class ConsoleDisplay:
220
217
 
221
218
  metadata_text = self._format_bottom_metadata(
222
219
  display_items,
223
- highlight_items,
220
+ highlight_index,
224
221
  config["highlight_color"],
225
222
  max_width=available,
226
223
  )
@@ -268,11 +265,11 @@ class ConsoleDisplay:
268
265
  from fast_agent.mcp.helpers.content_helpers import get_text, is_text_content
269
266
 
270
267
  # Determine the style based on message type
271
- # USER and ASSISTANT messages should display in normal style
268
+ # USER, ASSISTANT, and SYSTEM messages should display in normal style
272
269
  # TOOL_CALL and TOOL_RESULT should be dimmed
273
270
  if is_error:
274
271
  style = "dim red"
275
- elif message_type in [MessageType.USER, MessageType.ASSISTANT]:
272
+ elif message_type in [MessageType.USER, MessageType.ASSISTANT, MessageType.SYSTEM]:
276
273
  style = None # No style means default/normal white
277
274
  else:
278
275
  style = "dim"
@@ -464,7 +461,7 @@ class ConsoleDisplay:
464
461
  def _format_bottom_metadata(
465
462
  self,
466
463
  items: List[str],
467
- highlight_items: List[str],
464
+ highlight_index: int | None,
468
465
  highlight_color: str,
469
466
  max_width: int | None = None,
470
467
  ) -> Text:
@@ -473,8 +470,9 @@ class ConsoleDisplay:
473
470
 
474
471
  Args:
475
472
  items: List of items to display
476
- highlight_items: List of items to highlight
473
+ highlight_index: Index of item to highlight (0-based), or None for no highlighting
477
474
  highlight_color: Color to use for highlighting
475
+ max_width: Maximum width for the formatted text
478
476
 
479
477
  Returns:
480
478
  Formatted Text object with proper separators and highlighting
@@ -491,14 +489,7 @@ class ConsoleDisplay:
491
489
  sep = Text(" | ", style="dim") if i > 0 else Text("")
492
490
 
493
491
  # Prepare item text with potential highlighting
494
- should_highlight = False
495
- if item in highlight_items:
496
- should_highlight = True
497
- else:
498
- for highlight in highlight_items:
499
- if item.startswith(highlight) or highlight.endswith(item):
500
- should_highlight = True
501
- break
492
+ should_highlight = highlight_index is not None and i == highlight_index
502
493
 
503
494
  item_text = Text(item, style=(highlight_color if should_highlight else "dim"))
504
495
 
@@ -569,7 +560,7 @@ class ConsoleDisplay:
569
560
  tool_name: str,
570
561
  tool_args: Dict[str, Any] | None,
571
562
  bottom_items: List[str] | None = None,
572
- highlight_items: str | List[str] | None = None,
563
+ highlight_index: int | None = None,
573
564
  max_item_length: int | None = None,
574
565
  name: str | None = None,
575
566
  ) -> None:
@@ -579,7 +570,7 @@ class ConsoleDisplay:
579
570
  tool_name: Name of the tool being called
580
571
  tool_args: Arguments being passed to the tool
581
572
  bottom_items: Optional list of items for bottom separator (e.g., available tools)
582
- highlight_items: Item(s) to highlight in the bottom separator
573
+ highlight_index: Index of item to highlight in the bottom separator (0-based), or None
583
574
  max_item_length: Optional max length for bottom items (with ellipsis)
584
575
  name: Optional agent name
585
576
  """
@@ -596,7 +587,7 @@ class ConsoleDisplay:
596
587
  name=name,
597
588
  right_info=right_info,
598
589
  bottom_metadata=bottom_items,
599
- highlight_items=tool_name,
590
+ highlight_index=highlight_index,
600
591
  max_item_length=max_item_length,
601
592
  truncate_content=True,
602
593
  )
@@ -683,7 +674,7 @@ class ConsoleDisplay:
683
674
  self,
684
675
  message_text: Union[str, Text, "PromptMessageExtended"],
685
676
  bottom_items: List[str] | None = None,
686
- highlight_items: str | List[str] | None = None,
677
+ highlight_index: int | None = None,
687
678
  max_item_length: int | None = None,
688
679
  name: str | None = None,
689
680
  model: str | None = None,
@@ -694,7 +685,7 @@ class ConsoleDisplay:
694
685
  Args:
695
686
  message_text: The message content to display (str, Text, or PromptMessageExtended)
696
687
  bottom_items: Optional list of items for bottom separator (e.g., servers, destinations)
697
- highlight_items: Item(s) to highlight in the bottom separator
688
+ highlight_index: Index of item to highlight in the bottom separator (0-based), or None
698
689
  max_item_length: Optional max length for bottom items (with ellipsis)
699
690
  title: Title for the message (default "ASSISTANT")
700
691
  name: Optional agent name
@@ -722,7 +713,7 @@ class ConsoleDisplay:
722
713
  name=name,
723
714
  right_info=right_info,
724
715
  bottom_metadata=bottom_items,
725
- highlight_items=highlight_items,
716
+ highlight_index=highlight_index,
726
717
  max_item_length=max_item_length,
727
718
  truncate_content=False, # Assistant messages shouldn't be truncated
728
719
  additional_message=additional_message,
@@ -816,6 +807,32 @@ class ConsoleDisplay:
816
807
  truncate_content=False, # User messages typically shouldn't be truncated
817
808
  )
818
809
 
810
+ def show_system_message(
811
+ self,
812
+ system_prompt: str,
813
+ agent_name: str | None = None,
814
+ server_count: int = 0,
815
+ ) -> None:
816
+ """Display the system prompt in a formatted panel."""
817
+ if not self.config or not self.config.logger.show_chat:
818
+ return
819
+
820
+ # Build right side info
821
+ right_parts = []
822
+ if server_count > 0:
823
+ server_word = "server" if server_count == 1 else "servers"
824
+ right_parts.append(f"{server_count} MCP {server_word}")
825
+
826
+ right_info = f"[dim]{' '.join(right_parts)}[/dim]" if right_parts else ""
827
+
828
+ self.display_message(
829
+ content=system_prompt,
830
+ message_type=MessageType.SYSTEM,
831
+ name=agent_name,
832
+ right_info=right_info,
833
+ truncate_content=False, # Don't truncate system prompts
834
+ )
835
+
819
836
  async def show_prompt_loaded(
820
837
  self,
821
838
  prompt_name: str,
@@ -3,12 +3,13 @@ Enhanced prompt functionality with advanced prompt_toolkit features.
3
3
  """
4
4
 
5
5
  import asyncio
6
+ import json
6
7
  import os
7
8
  import shlex
8
9
  import subprocess
9
10
  import tempfile
10
11
  from importlib.metadata import version
11
- from typing import List, Optional
12
+ from typing import TYPE_CHECKING, List, Optional
12
13
 
13
14
  from prompt_toolkit import PromptSession
14
15
  from prompt_toolkit.completion import Completer, Completion, WordCompleter
@@ -20,9 +21,13 @@ from prompt_toolkit.styles import Style
20
21
  from rich import print as rich_print
21
22
 
22
23
  from fast_agent.agents.agent_types import AgentType
24
+ from fast_agent.constants import FAST_AGENT_ERROR_CHANNEL, FAST_AGENT_REMOVED_METADATA_CHANNEL
23
25
  from fast_agent.core.exceptions import PromptExitError
24
26
  from fast_agent.llm.model_info import get_model_info
25
27
 
28
+ if TYPE_CHECKING:
29
+ from fast_agent.core.agent_app import AgentApp
30
+
26
31
  # Get the application version
27
32
  try:
28
33
  app_version = version("fast-agent-mcp")
@@ -38,6 +43,30 @@ available_agents = set()
38
43
  # Keep track of multi-line mode state
39
44
  in_multiline_mode = False
40
45
 
46
+
47
+ def _extract_alert_flags_from_meta(blocks) -> set[str]:
48
+ flags: set[str] = set()
49
+ for block in blocks or []:
50
+ text = getattr(block, "text", None)
51
+ if not text:
52
+ continue
53
+ try:
54
+ payload = json.loads(text)
55
+ except (TypeError, ValueError):
56
+ continue
57
+ if payload.get("type") != "fast-agent-removed":
58
+ continue
59
+ category = payload.get("category")
60
+ match category:
61
+ case "text":
62
+ flags.add("T")
63
+ case "document":
64
+ flags.add("D")
65
+ case "vision":
66
+ flags.add("V")
67
+ return flags
68
+
69
+
41
70
  # Track whether help text has been shown globally
42
71
  help_message_shown = False
43
72
 
@@ -45,20 +74,17 @@ help_message_shown = False
45
74
  _agent_info_shown = set()
46
75
 
47
76
 
48
- async def _display_agent_info_helper(agent_name: str, agent_provider: object) -> None:
77
+ async def _display_agent_info_helper(agent_name: str, agent_provider: "AgentApp | None") -> None:
49
78
  """Helper function to display agent information."""
50
79
  # Only show once per agent
51
80
  if agent_name in _agent_info_shown:
52
81
  return
53
82
 
54
83
  try:
55
- # Get agent info
56
- if hasattr(agent_provider, "_agent"):
57
- # This is an AgentApp - get the specific agent
58
- agent = agent_provider._agent(agent_name)
59
- else:
60
- # This is a single agent
61
- agent = agent_provider
84
+ # Get agent info from AgentApp
85
+ if agent_provider is None:
86
+ return
87
+ agent = agent_provider._agent(agent_name)
62
88
 
63
89
  # Get counts TODO -- add this to the type library or adjust the way aggregator/reporting works
64
90
  server_count = 0
@@ -148,7 +174,9 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: object) ->
148
174
  pass
149
175
 
150
176
 
151
- async def _display_all_agents_with_hierarchy(available_agents: List[str], agent_provider) -> None:
177
+ async def _display_all_agents_with_hierarchy(
178
+ available_agents: List[str], agent_provider: "AgentApp | None"
179
+ ) -> None:
152
180
  """Display all agents with tree structure for workflow agents."""
153
181
  # Track which agents are children to avoid displaying them twice
154
182
  child_agents = set()
@@ -156,10 +184,9 @@ async def _display_all_agents_with_hierarchy(available_agents: List[str], agent_
156
184
  # First pass: identify all child agents
157
185
  for agent_name in available_agents:
158
186
  try:
159
- if hasattr(agent_provider, "_agent"):
160
- agent = agent_provider._agent(agent_name)
161
- else:
162
- agent = agent_provider
187
+ if agent_provider is None:
188
+ continue
189
+ agent = agent_provider._agent(agent_name)
163
190
 
164
191
  if agent.agent_type == AgentType.PARALLEL:
165
192
  if hasattr(agent, "fan_out_agents") and agent.fan_out_agents:
@@ -184,10 +211,9 @@ async def _display_all_agents_with_hierarchy(available_agents: List[str], agent_
184
211
  continue
185
212
 
186
213
  try:
187
- if hasattr(agent_provider, "_agent"):
188
- agent = agent_provider._agent(agent_name)
189
- else:
190
- agent = agent_provider
214
+ if agent_provider is None:
215
+ continue
216
+ agent = agent_provider._agent(agent_name)
191
217
 
192
218
  # Display parent agent
193
219
  await _display_agent_info_helper(agent_name, agent_provider)
@@ -202,7 +228,7 @@ async def _display_all_agents_with_hierarchy(available_agents: List[str], agent_
202
228
  continue
203
229
 
204
230
 
205
- async def _display_parallel_children(parallel_agent, agent_provider) -> None:
231
+ async def _display_parallel_children(parallel_agent, agent_provider: "AgentApp | None") -> None:
206
232
  """Display child agents of a parallel agent in tree format."""
207
233
  children = []
208
234
 
@@ -222,7 +248,7 @@ async def _display_parallel_children(parallel_agent, agent_provider) -> None:
222
248
  await _display_child_agent_info(child_agent, prefix, agent_provider)
223
249
 
224
250
 
225
- async def _display_router_children(router_agent, agent_provider) -> None:
251
+ async def _display_router_children(router_agent, agent_provider: "AgentApp | None") -> None:
226
252
  """Display child agents of a router agent in tree format."""
227
253
  children = []
228
254
 
@@ -239,7 +265,9 @@ async def _display_router_children(router_agent, agent_provider) -> None:
239
265
  await _display_child_agent_info(child_agent, prefix, agent_provider)
240
266
 
241
267
 
242
- async def _display_child_agent_info(child_agent, prefix: str, agent_provider) -> None:
268
+ async def _display_child_agent_info(
269
+ child_agent, prefix: str, agent_provider: "AgentApp | None"
270
+ ) -> None:
243
271
  """Display info for a child agent with tree prefix."""
244
272
  try:
245
273
  # Get counts for child agent
@@ -297,6 +325,7 @@ class AgentCompleter(Completer):
297
325
  "tools": "List available MCP tools",
298
326
  "prompt": "List and choose MCP prompts, or apply specific prompt (/prompt <name>)",
299
327
  "agents": "List available agents",
328
+ "system": "Show the current system prompt",
300
329
  "usage": "Show current usage statistics",
301
330
  "markdown": "Show last assistant message without markdown formatting",
302
331
  "save_history": "Save history; .json = MCP JSON, others = Markdown",
@@ -424,7 +453,9 @@ def get_text_from_editor(initial_text: str = "") -> str:
424
453
  return edited_text.strip() # Added strip() to remove trailing newlines often added by editors
425
454
 
426
455
 
427
- def create_keybindings(on_toggle_multiline=None, app=None, agent_provider=None, agent_name=None):
456
+ def create_keybindings(
457
+ on_toggle_multiline=None, app=None, agent_provider: "AgentApp | None" = None, agent_name=None
458
+ ):
428
459
  """Create custom key bindings."""
429
460
  kb = KeyBindings()
430
461
 
@@ -503,30 +534,20 @@ def create_keybindings(on_toggle_multiline=None, app=None, agent_provider=None,
503
534
  """Ctrl+Y: Copy last assistant response to clipboard."""
504
535
  if kb.agent_provider and kb.current_agent_name:
505
536
  try:
506
- # Get the agent
507
- if hasattr(kb.agent_provider, "_agent"):
508
- agent = kb.agent_provider._agent(kb.current_agent_name)
509
- else:
510
- agent = kb.agent_provider
511
-
512
- # Get message history
513
- if hasattr(agent, "_llm") and agent._llm and agent._llm.message_history:
514
- # Find last assistant message
515
- for msg in reversed(agent._llm.message_history):
516
- if msg.role == "assistant":
517
- content = msg.last_text()
518
- import pyperclip
519
-
520
- pyperclip.copy(content)
521
- rich_print("\n[green]✓ Copied to clipboard[/green]")
522
- return
523
-
524
- else:
525
- pass
537
+ # Get the agent from AgentApp
538
+ agent = kb.agent_provider._agent(kb.current_agent_name)
539
+
540
+ # Find last assistant message
541
+ for msg in reversed(agent.message_history):
542
+ if msg.role == "assistant":
543
+ content = msg.last_text()
544
+ import pyperclip
545
+
546
+ pyperclip.copy(content)
547
+ rich_print("\n[green]✓ Copied to clipboard[/green]")
548
+ return
526
549
  except Exception:
527
550
  pass
528
- else:
529
- pass
530
551
 
531
552
  return kb
532
553
 
@@ -541,7 +562,7 @@ async def get_enhanced_input(
541
562
  agent_types: dict[str, AgentType] = None,
542
563
  is_human_input: bool = False,
543
564
  toolbar_color: str = "ansiblue",
544
- agent_provider: object = None,
565
+ agent_provider: "AgentApp | None" = None,
545
566
  ) -> str:
546
567
  """
547
568
  Enhanced input with advanced prompt_toolkit features.
@@ -556,7 +577,7 @@ async def get_enhanced_input(
556
577
  agent_types: Dictionary mapping agent names to their types for display
557
578
  is_human_input: Whether this is a human input request (disables agent selection features)
558
579
  toolbar_color: Color to use for the agent name in the toolbar (default: "ansiblue")
559
- agent_provider: Optional agent provider for displaying agent info
580
+ agent_provider: Optional AgentApp for displaying agent info
560
581
 
561
582
  Returns:
562
583
  User input string
@@ -597,18 +618,22 @@ async def get_enhanced_input(
597
618
 
598
619
  shortcut_text = " | ".join(f"{key}:{action}" for key, action in shortcuts)
599
620
 
600
- # Resolve model name and TDV from the current agent if available
621
+ # Resolve model name, turn counter, and TDV from the current agent if available
601
622
  model_display = None
602
623
  tdv_segment = None
624
+ turn_count = 0
603
625
  try:
604
- agent_obj = (
605
- agent_provider._agent(agent_name)
606
- if agent_provider and hasattr(agent_provider, "_agent")
607
- else agent_provider
608
- )
609
- if agent_obj and hasattr(agent_obj, "llm") and agent_obj.llm:
610
- model_name = getattr(agent_obj.llm, "model_name", None)
611
- if model_name:
626
+ if agent_provider:
627
+ agent = agent_provider._agent(agent_name)
628
+
629
+ # Get turn count from message history
630
+ for message in agent.message_history:
631
+ if message.role == "user":
632
+ turn_count += 1
633
+
634
+ # Get model name from LLM
635
+ if agent.llm and agent.llm.model_name:
636
+ model_name = agent.llm.model_name
612
637
  # Truncate model name to max 25 characters with ellipsis
613
638
  max_len = 25
614
639
  if len(model_name) > max_len:
@@ -618,20 +643,35 @@ async def get_enhanced_input(
618
643
  model_display = model_name
619
644
 
620
645
  # Build TDV capability segment based on model database
621
- info = get_model_info(agent_obj)
646
+ info = get_model_info(agent)
622
647
  # Default to text-only if info resolution fails for any reason
623
648
  t, d, v = (True, False, False)
624
649
  if info:
625
650
  t, d, v = info.tdv_flags
626
651
 
652
+ # Check for alert flags in user messages
653
+ alert_flags: set[str] = set()
654
+ error_seen = False
655
+ for message in agent.message_history:
656
+ if message.channels:
657
+ if message.channels.get(FAST_AGENT_ERROR_CHANNEL):
658
+ error_seen = True
659
+ if message.role == "user" and message.channels:
660
+ meta_blocks = message.channels.get(FAST_AGENT_REMOVED_METADATA_CHANNEL, [])
661
+ alert_flags.update(_extract_alert_flags_from_meta(meta_blocks))
662
+
663
+ if error_seen and not alert_flags:
664
+ alert_flags.add("T")
665
+
627
666
  def _style_flag(letter: str, supported: bool) -> str:
628
667
  # Enabled uses the same color as NORMAL mode (ansigreen), disabled is dim
668
+ if letter in alert_flags:
669
+ return f"<style fg='ansired' bg='ansiblack'>{letter}</style>"
670
+
629
671
  enabled_color = "ansigreen"
630
- return (
631
- f"<style fg='{enabled_color}' bg='ansiblack'>{letter}</style>"
632
- if supported
633
- else f"<style fg='ansiblack' bg='ansiwhite'>{letter}</style>"
634
- )
672
+ if supported:
673
+ return f"<style fg='{enabled_color}' bg='ansiblack'>{letter}</style>"
674
+ return f"<style fg='ansiblack' bg='ansiwhite'>{letter}</style>"
635
675
 
636
676
  tdv_segment = f"{_style_flag('T', t)}{_style_flag('D', d)}{_style_flag('V', v)}"
637
677
  except Exception:
@@ -639,7 +679,7 @@ async def get_enhanced_input(
639
679
  model_display = None
640
680
  tdv_segment = None
641
681
 
642
- # Build dynamic middle segments: model (in green) and optional shortcuts
682
+ # Build dynamic middle segments: model (in green), turn counter, and optional shortcuts
643
683
  middle_segments = []
644
684
  if model_display:
645
685
  # Model chip + inline TDV flags
@@ -649,6 +689,10 @@ async def get_enhanced_input(
649
689
  )
650
690
  else:
651
691
  middle_segments.append(f"<style bg='ansigreen'>{model_display}</style>")
692
+
693
+ # Add turn counter (formatted as 3 digits)
694
+ middle_segments.append(f"{turn_count:03d}")
695
+
652
696
  if shortcut_text:
653
697
  middle_segments.append(shortcut_text)
654
698
  middle = " | ".join(middle_segments)
@@ -751,6 +795,8 @@ async def get_enhanced_input(
751
795
  return "CLEAR"
752
796
  elif cmd == "agents":
753
797
  return "LIST_AGENTS"
798
+ elif cmd == "system":
799
+ return "SHOW_SYSTEM"
754
800
  elif cmd == "usage":
755
801
  return "SHOW_USAGE"
756
802
  elif cmd == "markdown":
@@ -758,7 +804,9 @@ async def get_enhanced_input(
758
804
  elif cmd in ("save_history", "save"):
759
805
  # Return a structured action for the interactive loop to handle
760
806
  # Prefer programmatic saving via HistoryExporter; fall back to magic-string there if needed
761
- filename = cmd_parts[1].strip() if len(cmd_parts) > 1 and cmd_parts[1].strip() else None
807
+ filename = (
808
+ cmd_parts[1].strip() if len(cmd_parts) > 1 and cmd_parts[1].strip() else None
809
+ )
762
810
  return {"save_history": True, "filename": filename}
763
811
  elif cmd == "prompt":
764
812
  # Handle /prompt with no arguments as interactive mode
@@ -933,11 +981,14 @@ async def handle_special_commands(command, agent_app=None):
933
981
  rich_print(" /help - Show this help")
934
982
  rich_print(" /clear - Clear screen")
935
983
  rich_print(" /agents - List available agents")
984
+ rich_print(" /system - Show the current system prompt")
936
985
  rich_print(" /prompt <name> - Apply a specific prompt by name")
937
986
  rich_print(" /usage - Show current usage statistics")
938
987
  rich_print(" /markdown - Show last assistant message without markdown formatting")
939
988
  rich_print(" /save_history <filename> - Save current chat history to a file")
940
- rich_print(" [dim]Tip: Use a .json extension for MCP-compatible JSON; any other extension saves Markdown.[/dim]")
989
+ rich_print(
990
+ " [dim]Tip: Use a .json extension for MCP-compatible JSON; any other extension saves Markdown.[/dim]"
991
+ )
941
992
  rich_print(" @agent_name - Switch to agent")
942
993
  rich_print(" STOP - Return control back to the workflow")
943
994
  rich_print(" EXIT - Exit fast-agent, terminating any running workflows")
@@ -972,6 +1023,10 @@ async def handle_special_commands(command, agent_app=None):
972
1023
  # Return a dictionary to signal that usage should be shown
973
1024
  return {"show_usage": True}
974
1025
 
1026
+ elif command == "SHOW_SYSTEM":
1027
+ # Return a dictionary to signal that system prompt should be shown
1028
+ return {"show_system": True}
1029
+
975
1030
  elif command == "MARKDOWN":
976
1031
  # Return a dictionary to signal that markdown display should be shown
977
1032
  return {"show_markdown": True}