agentcrew-ai 0.8.2__py3-none-any.whl → 0.8.4__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 (47) 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 +11 -0
  8. AgentCrew/modules/browser_automation/element_extractor.py +4 -3
  9. AgentCrew/modules/browser_automation/js/draw_element_boxes.js +200 -0
  10. AgentCrew/modules/browser_automation/js/extract_clickable_elements.js +58 -26
  11. AgentCrew/modules/browser_automation/js/extract_elements_by_text.js +21 -19
  12. AgentCrew/modules/browser_automation/js/extract_input_elements.js +22 -23
  13. AgentCrew/modules/browser_automation/js/filter_hidden_elements.js +104 -0
  14. AgentCrew/modules/browser_automation/js/remove_element_boxes.js +29 -0
  15. AgentCrew/modules/browser_automation/js_loader.py +385 -92
  16. AgentCrew/modules/browser_automation/service.py +118 -347
  17. AgentCrew/modules/browser_automation/tool.py +28 -29
  18. AgentCrew/modules/chat/message/command_processor.py +7 -1
  19. AgentCrew/modules/chat/message/conversation.py +9 -8
  20. AgentCrew/modules/code_analysis/service.py +39 -0
  21. AgentCrew/modules/code_analysis/tool.py +10 -1
  22. AgentCrew/modules/console/command_handlers.py +186 -1
  23. AgentCrew/modules/console/completers.py +67 -0
  24. AgentCrew/modules/console/console_ui.py +59 -5
  25. AgentCrew/modules/console/display_handlers.py +12 -0
  26. AgentCrew/modules/console/input_handler.py +2 -0
  27. AgentCrew/modules/console/ui_effects.py +3 -4
  28. AgentCrew/modules/custom_llm/service.py +25 -3
  29. AgentCrew/modules/file_editing/tool.py +9 -11
  30. AgentCrew/modules/google/native_service.py +13 -0
  31. AgentCrew/modules/gui/widgets/message_bubble.py +1 -6
  32. AgentCrew/modules/llm/constants.py +38 -1
  33. AgentCrew/modules/llm/model_registry.py +9 -0
  34. AgentCrew/modules/llm/types.py +12 -1
  35. AgentCrew/modules/memory/base_service.py +2 -2
  36. AgentCrew/modules/memory/chroma_service.py +79 -138
  37. AgentCrew/modules/memory/context_persistent.py +10 -4
  38. AgentCrew/modules/memory/tool.py +17 -18
  39. AgentCrew/modules/openai/response_service.py +19 -11
  40. AgentCrew/modules/openai/service.py +15 -0
  41. AgentCrew/modules/prompts/constants.py +27 -14
  42. {agentcrew_ai-0.8.2.dist-info → agentcrew_ai-0.8.4.dist-info}/METADATA +3 -3
  43. {agentcrew_ai-0.8.2.dist-info → agentcrew_ai-0.8.4.dist-info}/RECORD +47 -43
  44. {agentcrew_ai-0.8.2.dist-info → agentcrew_ai-0.8.4.dist-info}/WHEEL +0 -0
  45. {agentcrew_ai-0.8.2.dist-info → agentcrew_ai-0.8.4.dist-info}/entry_points.txt +0 -0
  46. {agentcrew_ai-0.8.2.dist-info → agentcrew_ai-0.8.4.dist-info}/licenses/LICENSE +0 -0
  47. {agentcrew_ai-0.8.2.dist-info → agentcrew_ai-0.8.4.dist-info}/top_level.txt +0 -0
@@ -52,13 +52,11 @@ def get_browser_navigate_tool_definition(provider="claude") -> Dict[str, Any]:
52
52
 
53
53
  def get_browser_click_tool_definition(provider="claude") -> Dict[str, Any]:
54
54
  """Get tool definition for browser element clicking."""
55
- tool_description = (
56
- "Click an element using its UUID. Get UUIDs from browser_get_content first."
57
- )
55
+ tool_description = "Click an element using its UUID. Get UUIDs from get_browser_content tool result first."
58
56
  tool_arguments = {
59
57
  "element_uuid": {
60
58
  "type": "string",
61
- "description": "UUID identifier from browser_get_content clickable elements table.",
59
+ "description": "UUID identifier from get_browser_content tool result clickable elements table.",
62
60
  }
63
61
  }
64
62
  tool_required = ["element_uuid"]
@@ -109,9 +107,10 @@ def get_browser_scroll_tool_definition(provider="claude") -> Dict[str, Any]:
109
107
  "element_uuid": {
110
108
  "type": "string",
111
109
  "description": "Optional UUID for specific element to scroll. Scrolls document if not provided.",
110
+ "default": "document",
112
111
  },
113
112
  }
114
- tool_required = ["direction"]
113
+ tool_required = ["direction", "element_uuid"]
115
114
 
116
115
  if provider == "claude":
117
116
  return {
@@ -142,7 +141,7 @@ def get_browser_get_content_tool_definition(provider="claude") -> Dict[str, Any]
142
141
  """Get tool definition for browser content extraction."""
143
142
  tool_description = (
144
143
  "Extract page content as markdown with tables of clickable, input, and scrollable elements. UUIDs reset on each call."
145
- "browser_get_content result is UNIQUE in whole conversation. Remember to summarize important information before calling again."
144
+ "get_browser_content tool's result is UNIQUE in whole conversation. Remember to summarize important information before calling again."
146
145
  )
147
146
  tool_arguments = {}
148
147
  tool_required = []
@@ -196,7 +195,7 @@ def get_browser_get_content_tool_handler(
196
195
  tool_result.append(context_image.get("screenshot", {}))
197
196
  return tool_result
198
197
  else:
199
- raise RuntimeError(f"Content extraction failed: {result['error']}")
198
+ raise RuntimeError(f"Content extraction failed: {result['error']}")
200
199
 
201
200
  return handle_browser_get_content
202
201
 
@@ -221,9 +220,9 @@ def get_browser_navigate_tool_handler(
221
220
  if result.get("profile")
222
221
  else ""
223
222
  )
224
- return f"{result.get('message', 'Success')}. Use `browser_get_content` to read the url content.\nCurrent URL: {result.get('current_url', 'Unknown')}{profile_info}"
223
+ return f"{result.get('message', 'Success')}. Call `get_browser_content` tool to read the url content.\nCurrent URL: {result.get('current_url', 'Unknown')}{profile_info}"
225
224
  else:
226
- raise RuntimeError(f"Navigation failed: {result['error']}")
225
+ raise RuntimeError(f"Navigation failed: {result['error']}")
227
226
 
228
227
  return handle_browser_navigate
229
228
 
@@ -241,16 +240,15 @@ def get_browser_click_tool_handler(
241
240
 
242
241
  result = browser_service.click_element(element_uuid)
243
242
 
244
- diff_summary = _get_content_delta_changes(browser_service)
245
-
246
243
  if result.get("success", True):
244
+ diff_summary = _get_content_delta_changes(browser_service)
247
245
  return (
248
- f"{result.get('message', 'Success')}. Use `browser_get_content` to get the updated content.\n"
246
+ f"{result.get('message', 'Success')}. Call `get_browser_content` tool to get the updated content.\n"
249
247
  f"UUID: {element_uuid}\nClickedElement: {result.get('elementInfo', {}).get('text', 'Unknown')}.\n"
250
248
  f"Content delta changes:\n{diff_summary}"
251
249
  )
252
250
  else:
253
- return f"Click failed: {result['error']}\nUUID: {element_uuid}.\nUse `browser_get_content` to get the updated UUID"
251
+ return f"Click failed: {result['error']}\nUUID: {element_uuid}.\nCall `get_browser_content` tool to get the updated UUID"
254
252
 
255
253
  return handle_browser_click
256
254
 
@@ -273,24 +271,26 @@ def get_browser_scroll_tool_handler(
273
271
  "Error: Invalid scroll direction. Use 'up', 'down', 'left', or 'right'."
274
272
  )
275
273
 
276
- result = browser_service.scroll_page(direction, amount, element_uuid)
274
+ result = browser_service.scroll_page(
275
+ direction, amount, element_uuid if element_uuid != "document" else None
276
+ )
277
277
 
278
278
  if result.get("success", True):
279
- return f"{result.get('message', 'Success')}, Use `browser_get_content` to get the updated content."
279
+ return f"{result.get('message', 'Success')}, Call `get_browser_content` tool to get the updated content."
280
280
  else:
281
281
  uuid_info = f"\nUUID: {element_uuid}" if element_uuid else ""
282
- raise RuntimeError(f"Scroll failed: {result['error']}{uuid_info}")
282
+ raise RuntimeError(f"Scroll failed: {result['error']}{uuid_info}")
283
283
 
284
284
  return handle_browser_scroll
285
285
 
286
286
 
287
287
  def get_browser_input_tool_definition(provider="claude") -> Dict[str, Any]:
288
288
  """Get tool definition for browser input."""
289
- tool_description = "Input data into form fields using UUID. Get UUIDs from browser_get_content first."
289
+ tool_description = "Input data into form fields using UUID. Get UUIDs from get_browser_content tool first."
290
290
  tool_arguments = {
291
291
  "element_uuid": {
292
292
  "type": "string",
293
- "description": "UUID identifier from browser_get_content input elements table.",
293
+ "description": "UUID identifier from get_browser_content tool result's input elements table.",
294
294
  },
295
295
  "value": {
296
296
  "type": "string",
@@ -340,13 +340,13 @@ def get_browser_input_tool_handler(
340
340
  return "Error: No value provided for input."
341
341
 
342
342
  result = browser_service.input_data(element_uuid, str(value))
343
- diff_summary = _get_content_delta_changes(browser_service)
344
343
 
345
344
  if result.get("success", True):
346
- return f"✅ {result.get('message', 'Success')}\nUUID: {element_uuid}\nValue: {value}\nContent delta changes:\n{diff_summary}"
345
+ diff_summary = _get_content_delta_changes(browser_service)
346
+ return f"{result.get('message', 'Success')}\nUUID: {element_uuid}\nValue: {value}\nContent delta changes:\n{diff_summary}"
347
347
  else:
348
348
  raise RuntimeError(
349
- f"Input failed: {result['error']}\nUUID: {element_uuid}\nValue: {value}.\n Use `browser_get_content` to get updated UUID."
349
+ f"Input failed: {result['error']}\nUUID: {element_uuid}\nValue: {value}.\n Call `get_browser_content` tool to get updated UUID."
350
350
  )
351
351
 
352
352
  return handle_browser_input
@@ -405,16 +405,15 @@ def get_browser_get_elements_by_text_tool_handler(
405
405
  if result.get("success", False):
406
406
  elements_found = result.get("elements_found", 0)
407
407
  if elements_found == 0:
408
- return f"No elements found containing text: '{text}'"
408
+ return f"No elements found containing text: '{text}'"
409
409
 
410
410
  content = result.get("content", "")
411
411
  return (
412
- f"Found {elements_found} elements containing text: '{text}'\n"
413
- + content
412
+ f"Found {elements_found} elements containing text: '{text}'\n" + content
414
413
  )
415
414
  else:
416
415
  raise RuntimeError(
417
- f"Search failed: {result.get('error', 'Unknown error')}\nSearch text: '{text}'"
416
+ f"Search failed: {result.get('error', 'Unknown error')}\nSearch text: '{text}'"
418
417
  )
419
418
 
420
419
  return handle_browser_get_elements_by_text
@@ -422,7 +421,7 @@ def get_browser_get_elements_by_text_tool_handler(
422
421
 
423
422
  def get_browser_capture_screenshot_tool_definition(provider="claude") -> Dict[str, Any]:
424
423
  """Get tool definition for browser screenshot capture."""
425
- tool_description = "Capture page screenshot as base64 image data. Supports different formats and full page capture."
424
+ tool_description = "Capture page screenshot as base64 image data with colored boxes and UUID labels drawn over all identified elements. Supports different formats and full page capture."
426
425
  tool_arguments = {
427
426
  "format": {
428
427
  "type": "string",
@@ -500,7 +499,7 @@ def get_browser_capture_screenshot_tool_handler(
500
499
  return [screenshot_data]
501
500
  else:
502
501
  raise RuntimeError(
503
- f"Screenshot capture failed: {result.get('error', 'Unknown error')}"
502
+ f"Screenshot capture failed: {result.get('error', 'Unknown error')}"
504
503
  )
505
504
 
506
505
  return handle_browser_capture_screenshot
@@ -617,13 +616,13 @@ def get_browser_send_key_tool_handler(
617
616
  else ""
618
617
  )
619
618
  diff_summary = _get_content_delta_changes(browser_service)
620
- success_msg = f"{result.get('message', 'Success')}. {key_info}\nContent delta changes:\n{diff_summary}"
619
+ success_msg = f"{result.get('message', 'Success')}. {key_info}\nContent delta changes:\n{diff_summary}"
621
620
  if modifiers_info:
622
621
  success_msg += f". {modifiers_info}"
623
622
  return success_msg
624
623
  else:
625
624
  raise RuntimeError(
626
- f"Key send failed: {result.get('error', 'Unknown error')}"
625
+ f"Key send failed: {result.get('error', 'Unknown error')}"
627
626
  )
628
627
 
629
628
  return handle_browser_send_key
@@ -105,8 +105,14 @@ class CommandProcessor:
105
105
  asssistant_messages_iterator = reversed(
106
106
  [
107
107
  msg
108
- for msg in self.message_handler.streamline_messages
108
+ for i, msg in enumerate(self.message_handler.streamline_messages)
109
109
  if msg.get("role") == "assistant"
110
+ and (
111
+ self.message_handler.streamline_messages[i + 1].get("role")
112
+ == "user"
113
+ if i + 1 < len(self.message_handler.streamline_messages)
114
+ else True
115
+ )
110
116
  ]
111
117
  )
112
118
  latest_assistant_blk = None
@@ -112,14 +112,6 @@ class ConversationManager:
112
112
  msg["tool_call_id"] = tool_result.get("tool_use_id", "")
113
113
 
114
114
  self.message_handler.current_conversation_id = conversation_id
115
- if self.message_handler.memory_service:
116
- self.message_handler.memory_service.session_id = (
117
- self.message_handler.current_conversation_id
118
- )
119
- self.message_handler.memory_service.loaded_conversation = True
120
- self.message_handler.memory_service.load_conversation_context(
121
- self.message_handler.current_conversation_id
122
- )
123
115
  last_agent_name = history[-1].get("agent", "")
124
116
  if last_agent_name and self.message_handler.agent_manager.select_agent(
125
117
  last_agent_name
@@ -129,6 +121,15 @@ class ConversationManager:
129
121
  )
130
122
  self.message_handler._notify("agent_changed", last_agent_name)
131
123
 
124
+ if self.message_handler.memory_service:
125
+ self.message_handler.memory_service.session_id = (
126
+ self.message_handler.current_conversation_id
127
+ )
128
+ self.message_handler.memory_service.loaded_conversation = True
129
+ self.message_handler.memory_service.load_conversation_context(
130
+ self.message_handler.current_conversation_id, last_agent_name
131
+ )
132
+
132
133
  self.message_handler.streamline_messages = history
133
134
  self.message_handler.agent_manager.rebuild_agents_messages(
134
135
  self.message_handler.streamline_messages
@@ -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