agentcrew-ai 0.8.3__py3-none-any.whl → 0.8.5__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.
Files changed (38) hide show
  1. AgentCrew/__init__.py +1 -1
  2. AgentCrew/main.py +3 -1
  3. AgentCrew/modules/a2a/agent_cards.py +8 -2
  4. AgentCrew/modules/a2a/errors.py +72 -0
  5. AgentCrew/modules/a2a/server.py +21 -2
  6. AgentCrew/modules/a2a/task_manager.py +180 -39
  7. AgentCrew/modules/agents/local_agent.py +1 -1
  8. AgentCrew/modules/browser_automation/element_extractor.py +1 -1
  9. AgentCrew/modules/browser_automation/js/extract_clickable_elements.js +2 -4
  10. AgentCrew/modules/browser_automation/service.py +17 -7
  11. AgentCrew/modules/chat/message/command_processor.py +9 -1
  12. AgentCrew/modules/chat/message/conversation.py +1 -0
  13. AgentCrew/modules/chat/message/handler.py +3 -6
  14. AgentCrew/modules/code_analysis/service.py +39 -0
  15. AgentCrew/modules/code_analysis/tool.py +10 -1
  16. AgentCrew/modules/console/command_handlers.py +186 -1
  17. AgentCrew/modules/console/completers.py +67 -0
  18. AgentCrew/modules/console/confirmation_handler.py +83 -38
  19. AgentCrew/modules/console/console_ui.py +86 -28
  20. AgentCrew/modules/console/diff_display.py +203 -0
  21. AgentCrew/modules/console/display_handlers.py +15 -0
  22. AgentCrew/modules/console/input_handler.py +1 -1
  23. AgentCrew/modules/console/tool_display.py +35 -4
  24. AgentCrew/modules/console/ui_effects.py +30 -14
  25. AgentCrew/modules/custom_llm/deepinfra_service.py +20 -19
  26. AgentCrew/modules/custom_llm/github_copilot_service.py +157 -2
  27. AgentCrew/modules/custom_llm/service.py +1 -9
  28. AgentCrew/modules/gui/widgets/message_bubble.py +1 -6
  29. AgentCrew/modules/llm/model_registry.py +1 -1
  30. AgentCrew/modules/memory/chroma_service.py +0 -1
  31. AgentCrew/modules/memory/context_persistent.py +10 -4
  32. AgentCrew/modules/memory/tool.py +2 -3
  33. {agentcrew_ai-0.8.3.dist-info → agentcrew_ai-0.8.5.dist-info}/METADATA +3 -3
  34. {agentcrew_ai-0.8.3.dist-info → agentcrew_ai-0.8.5.dist-info}/RECORD +38 -36
  35. {agentcrew_ai-0.8.3.dist-info → agentcrew_ai-0.8.5.dist-info}/WHEEL +0 -0
  36. {agentcrew_ai-0.8.3.dist-info → agentcrew_ai-0.8.5.dist-info}/entry_points.txt +0 -0
  37. {agentcrew_ai-0.8.3.dist-info → agentcrew_ai-0.8.5.dist-info}/licenses/LICENSE +0 -0
  38. {agentcrew_ai-0.8.3.dist-info → agentcrew_ai-0.8.5.dist-info}/top_level.txt +0 -0
@@ -279,7 +279,7 @@ class MessageHandler(Observable):
279
279
  # Notify about response progress
280
280
  if not self.agent.is_streaming():
281
281
  # Delays it a bit when using without stream
282
- time.sleep(0.5)
282
+ time.sleep(0.3)
283
283
  self._notify("response_chunk", (chunk_text, assistant_response))
284
284
  if voice_sentence is not None:
285
285
  if (
@@ -321,6 +321,8 @@ class MessageHandler(Observable):
321
321
  .lstrip("\n")
322
322
  )
323
323
 
324
+ self.stream_generator = None
325
+
324
326
  # End thinking when break the response stream
325
327
  if not end_thinking and start_thinking:
326
328
  self._notify("thinking_completed", thinking_content)
@@ -386,11 +388,6 @@ class MessageHandler(Observable):
386
388
 
387
389
  return await self.get_assistant_response()
388
390
 
389
- self.stream_generator = None
390
-
391
- if thinking_content:
392
- self._notify("agent_continue", self.agent.name)
393
-
394
391
  # Add assistant response to messages
395
392
  if assistant_response.strip():
396
393
  self._messages_append(
@@ -26,6 +26,7 @@ class CodeAnalysisService:
26
26
  ".cxx": "cpp",
27
27
  ".hxx": "cpp",
28
28
  ".rb": "ruby",
29
+ ".sh": "bash",
29
30
  ".rake": "ruby",
30
31
  ".go": "go",
31
32
  ".rs": "rust",
@@ -36,6 +37,7 @@ class CodeAnalysisService:
36
37
  ".json": "config",
37
38
  ".toml": "config",
38
39
  ".yaml": "config",
40
+ ".yml": "config",
39
41
  # Add more languages as needed
40
42
  }
41
43
 
@@ -784,6 +786,43 @@ class CodeAnalysisService:
784
786
  return result
785
787
  break # Only capture the first identifier
786
788
  return result
789
+ else:
790
+ if node.type in [
791
+ "type_declaration",
792
+ "function_declaration",
793
+ "method_declaration",
794
+ "interface_declaration",
795
+ ]:
796
+ for child in node.children:
797
+ if (
798
+ child.type == "identifier"
799
+ or child.type == "field_identifier"
800
+ ):
801
+ result["name"] = self._extract_node_text(
802
+ child, source_code
803
+ )
804
+ result["first_line"] = (
805
+ self._extract_node_text(node, source_code)
806
+ .split("\n")[0]
807
+ .strip("{")
808
+ )
809
+ return result
810
+ return result
811
+ elif (
812
+ node.type == "var_declaration"
813
+ or node.type == "const_declaration"
814
+ ):
815
+ # Handle Go variable and constant declarations
816
+ for child in node.children:
817
+ if child.type == "var_spec" or child.type == "const_spec":
818
+ for subchild in child.children:
819
+ if subchild.type == "identifier":
820
+ result["type"] = "variable_declaration"
821
+ result["name"] = self._extract_node_text(
822
+ subchild, source_code
823
+ )
824
+ return result
825
+ return result
787
826
 
788
827
  # Recursively process children
789
828
  children = []
@@ -72,7 +72,16 @@ def get_code_analysis_tool_handler(
72
72
  if isinstance(result, dict) and "error" in result:
73
73
  raise Exception(f"Failed to analyze code: {result['error']}")
74
74
 
75
- return result
75
+ return [
76
+ {
77
+ "type": "text",
78
+ "text": result,
79
+ },
80
+ {
81
+ "type": "text",
82
+ "text": "Base on the code analysis, learn about the patterns and development flows, adapt project behaviors if possible for better response.",
83
+ },
84
+ ]
76
85
 
77
86
  return handler
78
87
 
@@ -12,6 +12,7 @@ import subprocess
12
12
  import sys
13
13
 
14
14
  from rich.text import Text
15
+ from rich.table import Table
15
16
 
16
17
  from .constants import (
17
18
  RICH_STYLE_YELLOW,
@@ -19,7 +20,7 @@ from .constants import (
19
20
  from AgentCrew.modules.config.config_management import ConfigManagement
20
21
  from loguru import logger
21
22
 
22
- from typing import TYPE_CHECKING
23
+ from typing import TYPE_CHECKING, Dict
23
24
 
24
25
  if TYPE_CHECKING:
25
26
  from .console_ui import ConsoleUI
@@ -38,6 +39,7 @@ class CommandHandlers:
38
39
  """
39
40
  self.console = console_ui.console
40
41
  self.message_handler = console_ui.message_handler
42
+ self.context_service = console_ui.message_handler.persistent_service
41
43
 
42
44
  def open_file_in_editor(self, file_path: str) -> bool:
43
45
  """
@@ -278,3 +280,186 @@ class CommandHandlers:
278
280
  )
279
281
  )
280
282
  logger.error(f"Import agent error: {str(e)}", exc_info=True)
283
+
284
+ def handle_list_behaviors_command(self) -> None:
285
+ try:
286
+ if not self.context_service:
287
+ self.console.print(
288
+ Text(
289
+ "❌ Context persistence service not available", style="bold red"
290
+ )
291
+ )
292
+ return
293
+
294
+ global_behaviors = self.context_service.get_adaptive_behaviors(
295
+ self.message_handler.agent.name
296
+ )
297
+ project_behaviors = self.context_service.get_adaptive_behaviors(
298
+ self.message_handler.agent.name, is_local=True
299
+ )
300
+
301
+ if not global_behaviors and not project_behaviors:
302
+ self.console.print(
303
+ Text("ℹ️ No adaptive behaviors found.", style=RICH_STYLE_YELLOW)
304
+ )
305
+ return
306
+
307
+ self._display_behaviors_table(global_behaviors, project_behaviors)
308
+
309
+ except Exception as e:
310
+ self.console.print(
311
+ Text(f"❌ Error listing behaviors: {str(e)}", style="bold red")
312
+ )
313
+ logger.error(f"List behaviors error: {str(e)}", exc_info=True)
314
+
315
+ def _display_behaviors_table(
316
+ self,
317
+ global_behaviors: Dict[str, str],
318
+ project_behaviors: Dict[str, str],
319
+ ) -> None:
320
+ if global_behaviors:
321
+ global_table = Table(
322
+ title="🌍 Global Behaviors",
323
+ show_header=True,
324
+ header_style="bold cyan",
325
+ title_style="bold blue",
326
+ )
327
+ global_table.add_column("ID", style="yellow", no_wrap=True)
328
+ global_table.add_column("Behavior", style="white")
329
+
330
+ for behavior_id, behavior_text in global_behaviors.items():
331
+ global_table.add_row(behavior_id, behavior_text)
332
+
333
+ self.console.print(global_table)
334
+ self.console.print()
335
+
336
+ if project_behaviors:
337
+ project_table = Table(
338
+ title="📁 Project Behaviors",
339
+ show_header=True,
340
+ header_style="bold green",
341
+ title_style="bold magenta",
342
+ )
343
+ project_table.add_column("ID", style="yellow", no_wrap=True)
344
+ project_table.add_column("Behavior", style="white")
345
+
346
+ for behavior_id, behavior_text in project_behaviors.items():
347
+ project_table.add_row(behavior_id, behavior_text)
348
+
349
+ self.console.print(project_table)
350
+ self.console.print()
351
+
352
+ def handle_update_behavior_command(
353
+ self, behavior_id: str, behavior_text: str, scope: str = "global"
354
+ ) -> None:
355
+ try:
356
+ if not self.context_service:
357
+ self.console.print(
358
+ Text(
359
+ "❌ Context persistence service not available", style="bold red"
360
+ )
361
+ )
362
+ return
363
+
364
+ behavior_text = behavior_text.strip()
365
+
366
+ if not behavior_text:
367
+ self.console.print(
368
+ Text("❌ Behavior text cannot be empty", style="bold red")
369
+ )
370
+ return
371
+
372
+ behavior_lower = behavior_text.lower().strip()
373
+ if not behavior_lower.startswith("when"):
374
+ self.console.print(
375
+ Text(
376
+ "❌ Behavior must follow 'when..., [action]...' format",
377
+ style="bold red",
378
+ )
379
+ )
380
+ return
381
+
382
+ is_local = scope == "project"
383
+ success = self.context_service.store_adaptive_behavior(
384
+ self.message_handler.agent.name,
385
+ behavior_id,
386
+ behavior_text,
387
+ is_local=is_local,
388
+ )
389
+
390
+ if success:
391
+ self.console.print(
392
+ Text(
393
+ f"✅ Behavior '{behavior_id}' updated successfully ({scope} scope)",
394
+ style="bold green",
395
+ )
396
+ )
397
+ else:
398
+ self.console.print(Text("❌ Failed to save behavior", style="bold red"))
399
+
400
+ except ValueError as e:
401
+ self.console.print(
402
+ Text(f"❌ Invalid behavior format: {str(e)}", style="bold red")
403
+ )
404
+ except Exception as e:
405
+ self.console.print(
406
+ Text(f"❌ Error updating behavior: {str(e)}", style="bold red")
407
+ )
408
+ logger.error(f"Update behavior error: {str(e)}", exc_info=True)
409
+
410
+ def handle_delete_behavior_command(
411
+ self, behavior_id: str, scope: str = "global"
412
+ ) -> None:
413
+ try:
414
+ if not self.context_service:
415
+ self.console.print(
416
+ Text(
417
+ "❌ Context persistence service not available", style="bold red"
418
+ )
419
+ )
420
+ return
421
+
422
+ is_local = scope == "project"
423
+
424
+ success = self.context_service.remove_adaptive_behavior(
425
+ self.message_handler.agent.name, behavior_id, is_local=is_local
426
+ )
427
+ if success:
428
+ self.console.print(
429
+ Text(
430
+ f"✅ Behavior '{behavior_id}' deleted successfully",
431
+ style="bold green",
432
+ )
433
+ )
434
+ else:
435
+ self.console.print(
436
+ Text(
437
+ f"❌ Failed to delete behavior '{behavior_id}'",
438
+ style="bold red",
439
+ )
440
+ )
441
+
442
+ except Exception as e:
443
+ self.console.print(
444
+ Text(f"❌ Error deleting behavior: {str(e)}", style="bold red")
445
+ )
446
+ logger.error(f"Delete behavior error: {str(e)}", exc_info=True)
447
+
448
+ def get_all_behavior_ids(self) -> list[str]:
449
+ try:
450
+ if not self.context_service:
451
+ return []
452
+
453
+ all_behaviors = self.context_service.get_adaptive_behaviors(
454
+ self.message_handler.agent.name
455
+ ) | self.context_service.get_adaptive_behaviors(
456
+ self.message_handler.agent.name, is_local=True
457
+ )
458
+ behavior_ids = []
459
+
460
+ for id, _ in all_behaviors.items():
461
+ behavior_ids.extend(id)
462
+
463
+ return behavior_ids
464
+ except Exception:
465
+ return []
@@ -121,6 +121,7 @@ class ChatCompleter(Completer):
121
121
  self.jump_completer = JumpCompleter(message_handler)
122
122
  self.mcp_completer = MCPCompleter(message_handler)
123
123
  self.drop_completer = DropCompleter(message_handler)
124
+ self.behavior_completer = BehaviorIDCompleter(message_handler)
124
125
 
125
126
  def get_completions(self, document, complete_event):
126
127
  text = document.text
@@ -140,6 +141,8 @@ class ChatCompleter(Completer):
140
141
  yield from self.file_completer.get_completions(document, complete_event)
141
142
  elif text.startswith("/drop "):
142
143
  yield from self.drop_completer.get_completions(document, complete_event)
144
+ elif text.startswith(("/delete_behavior ", "/update_behavior ")):
145
+ yield from self.behavior_completer.get_completions(document, complete_event)
143
146
  elif text.startswith("/export_agent "):
144
147
  remaining_text = text[14:] # Remove "/export_agent "
145
148
 
@@ -221,6 +224,15 @@ class ChatCompleter(Completer):
221
224
  "/toggle_session_yolo",
222
225
  "Toggle Auto-Approve Mode for Tool Calls (this session only)",
223
226
  ),
227
+ ("/list_behaviors", "List all agent adaptive behaviors"),
228
+ (
229
+ "/update_behavior",
230
+ "Update or create an adaptive behavior (usage: /update_behavior <scope> <id> <behavior_text>)",
231
+ ),
232
+ (
233
+ "/delete_behavior",
234
+ "Delete an adaptive behavior (usage: /delete_behavior <id>)",
235
+ ),
224
236
  ("/exit", "Exit the application"),
225
237
  ("/quit", "Exit the application"),
226
238
  ]
@@ -305,6 +317,61 @@ class DropCompleter(Completer):
305
317
  )
306
318
 
307
319
 
320
+ class BehaviorIDCompleter(Completer):
321
+ """Completer that shows available behavior IDs when typing /delete_behavior or /update_behavior commands."""
322
+
323
+ def __init__(self, message_handler=None):
324
+ self.message_handler = message_handler
325
+
326
+ def get_completions(self, document, complete_event):
327
+ text = document.text
328
+
329
+ if text.startswith(
330
+ (
331
+ "/delete_behavior global ",
332
+ "/delete_behavior project ",
333
+ "/update_behavior global ",
334
+ "/update_behavior project ",
335
+ )
336
+ ):
337
+ word_before_cursor = document.get_word_before_cursor(
338
+ pattern=COMPLETER_PATTERN
339
+ )
340
+
341
+ if self.message_handler and self.message_handler.persistent_service:
342
+ try:
343
+ scope = text.split(" ")[1].strip()
344
+ all_behaviors = (
345
+ self.message_handler.persistent_service.get_adaptive_behaviors(
346
+ self.message_handler.agent.name, is_local=scope == "project"
347
+ )
348
+ )
349
+ behavior_ids = []
350
+ for id, _ in all_behaviors.items():
351
+ behavior_ids.append(id)
352
+
353
+ for behavior_id in behavior_ids:
354
+ if behavior_id.startswith(word_before_cursor):
355
+ yield Completion(
356
+ behavior_id,
357
+ start_position=-len(word_before_cursor),
358
+ display=behavior_id,
359
+ )
360
+ except Exception:
361
+ pass
362
+ elif text.startswith(("/delete_behavior ", "/update_behavior ")):
363
+ word_before_cursor = document.get_word_before_cursor(
364
+ pattern=COMPLETER_PATTERN
365
+ )
366
+ for scope in ["global", "project"]:
367
+ if scope.startswith(word_before_cursor):
368
+ yield Completion(
369
+ scope,
370
+ start_position=-len(word_before_cursor),
371
+ display=scope,
372
+ )
373
+
374
+
308
375
  class DirectoryListingCompleter(Completer):
309
376
  def __init__(self):
310
377
  # Use PathCompleter for the heavy lifting
@@ -11,6 +11,8 @@ from rich.box import HORIZONTALS
11
11
  from rich.console import Group
12
12
  import time
13
13
 
14
+ from .diff_display import DiffDisplay
15
+
14
16
  from .constants import (
15
17
  RICH_STYLE_BLUE,
16
18
  RICH_STYLE_BLUE_BOLD,
@@ -45,6 +47,20 @@ class ConfirmationHandler:
45
47
  self._handle_ask_tool(tool_use, confirmation_id, message_handler)
46
48
  return
47
49
 
50
+ # Special handling for 'write_or_edit_file' tool with search/replace blocks
51
+ if tool_use["name"] == "write_or_edit_file":
52
+ file_path = tool_use["input"].get("file_path", "")
53
+ text_or_blocks = tool_use["input"].get("text_or_search_replace_blocks", "")
54
+
55
+ if DiffDisplay.has_search_replace_blocks(text_or_blocks):
56
+ self._display_write_or_edit_file_diff(
57
+ tool_use, file_path, text_or_blocks
58
+ )
59
+ self._get_and_handle_tool_response(
60
+ tool_use, confirmation_id, message_handler
61
+ )
62
+ return
63
+
48
64
  tool_texts_group = []
49
65
  header = Text("🔧 Tool ", style=RICH_STYLE_YELLOW)
50
66
  header.append(tool_use["name"], style=RICH_STYLE_BLUE_BOLD)
@@ -72,8 +88,71 @@ class ConfirmationHandler:
72
88
  )
73
89
  )
74
90
 
75
- # Get user response
91
+ self._get_and_handle_tool_response(tool_use, confirmation_id, message_handler)
92
+
93
+ def _handle_ask_tool(self, tool_use, confirmation_id, message_handler):
94
+ """Handle the ask tool - display question and guided answers."""
95
+ question = tool_use["input"].get("question", "")
96
+ guided_answers = tool_use["input"].get("guided_answers", [])
97
+ if isinstance(guided_answers, str):
98
+ guided_answers = guided_answers.strip("\n ").splitlines()
99
+
100
+ guided_answers.append("Custom your answer")
101
+
102
+ self.input_handler._stop_input_thread()
103
+ # Display the question
104
+ self.console.print(
105
+ Text("\n❓ Agent is asking for clarification:", style=RICH_STYLE_BLUE_BOLD)
106
+ )
107
+ response = self.input_handler.get_choice_input(f"{question}", guided_answers)
108
+
109
+ if response == "Custom your answer":
110
+ custom_answer = self.input_handler.get_prompt_input("Input your answer:")
111
+ message_handler.resolve_tool_confirmation(
112
+ confirmation_id, {"action": "answer", "answer": custom_answer}
113
+ )
114
+ elif response:
115
+ message_handler.resolve_tool_confirmation(
116
+ confirmation_id, {"action": "answer", "answer": response}
117
+ )
118
+
119
+ else:
120
+ message_handler.resolve_tool_confirmation(
121
+ confirmation_id, {"action": "answer", "answer": "Cancelled by user"}
122
+ )
123
+
124
+ self._ui.start_loading_animation()
125
+
126
+ self.input_handler._start_input_thread()
127
+
128
+ def _display_write_or_edit_file_diff(self, tool_use, file_path, blocks_text):
129
+ """Display split diff view for write_or_edit_file tool."""
130
+ header = Text("📝 File Edit ", style=RICH_STYLE_YELLOW)
131
+ header.append(file_path, style=RICH_STYLE_BLUE_BOLD)
132
+ header.append(" - Search/Replace Blocks", style=RICH_STYLE_YELLOW)
133
+
134
+ self.console.print(
135
+ Panel(header, box=HORIZONTALS, border_style=RICH_STYLE_YELLOW)
136
+ )
137
+
138
+ blocks = DiffDisplay.parse_search_replace_blocks(blocks_text)
139
+
140
+ if not blocks:
141
+ self.console.print(
142
+ Text("No valid search/replace blocks found", style=RICH_STYLE_RED)
143
+ )
144
+ return
145
+
146
+ for block in blocks:
147
+ diff_table = DiffDisplay.create_split_diff_table(
148
+ block["search"], block["replace"], max_width=self.console.width - 4
149
+ )
150
+ self.console.print(diff_table)
151
+
152
+ def _get_and_handle_tool_response(self, tool_use, confirmation_id, message_handler):
153
+ """Get user response for tool confirmation and handle it."""
76
154
  self.input_handler._stop_input_thread()
155
+
77
156
  choices = [
78
157
  "yes",
79
158
  "no",
@@ -91,11 +170,11 @@ class ConfirmationHandler:
91
170
  confirmation_id, {"action": "approve"}
92
171
  )
93
172
  elif response == choices[1]:
94
- response = self.input_handler.get_prompt_input(
173
+ deny_reason = self.input_handler.get_prompt_input(
95
174
  "Please tell me why you are denying this tool: "
96
175
  )
97
176
  message_handler.resolve_tool_confirmation(
98
- confirmation_id, {"action": "deny", "reason": response}
177
+ confirmation_id, {"action": "deny", "reason": deny_reason}
99
178
  )
100
179
  elif response == choices[2]:
101
180
  message_handler.resolve_tool_confirmation(
@@ -120,44 +199,10 @@ class ConfirmationHandler:
120
199
  style=RICH_STYLE_YELLOW,
121
200
  )
122
201
  self.console.print(saved_text)
123
- self._ui.start_loading_animation()
124
- self.input_handler._start_input_thread()
125
- time.sleep(0.2) # Small delay to between tool calls
126
-
127
- def _handle_ask_tool(self, tool_use, confirmation_id, message_handler):
128
- """Handle the ask tool - display question and guided answers."""
129
- question = tool_use["input"].get("question", "")
130
- guided_answers = tool_use["input"].get("guided_answers", [])
131
- if isinstance(guided_answers, str):
132
- guided_answers = guided_answers.strip("\n ").splitlines()
133
-
134
- guided_answers.append("Custom your answer")
135
-
136
- self.input_handler._stop_input_thread()
137
- # Display the question
138
- self.console.print(
139
- Text("\n❓ Agent is asking for clarification:", style=RICH_STYLE_BLUE_BOLD)
140
- )
141
- response = self.input_handler.get_choice_input(f"{question}", guided_answers)
142
-
143
- if response == "Custom your answer":
144
- custom_answer = self.input_handler.get_prompt_input("Input your answer:")
145
- message_handler.resolve_tool_confirmation(
146
- confirmation_id, {"action": "answer", "answer": custom_answer}
147
- )
148
- elif response:
149
- message_handler.resolve_tool_confirmation(
150
- confirmation_id, {"action": "answer", "answer": response}
151
- )
152
-
153
- else:
154
- message_handler.resolve_tool_confirmation(
155
- confirmation_id, {"action": "answer", "answer": "Cancelled by user"}
156
- )
157
202
 
158
203
  self._ui.start_loading_animation()
159
-
160
204
  self.input_handler._start_input_thread()
205
+ time.sleep(0.2)
161
206
 
162
207
  def display_mcp_prompt_confirmation(self, prompt_data, input_queue):
163
208
  """Display MCP prompt confirmation request and get user response."""