dtSpark 1.1.0a3__py3-none-any.whl → 1.1.0a7__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 (54) hide show
  1. dtSpark/_version.txt +1 -1
  2. dtSpark/aws/authentication.py +1 -1
  3. dtSpark/aws/bedrock.py +238 -239
  4. dtSpark/aws/costs.py +9 -5
  5. dtSpark/aws/pricing.py +25 -21
  6. dtSpark/cli_interface.py +77 -68
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +114 -91
  9. dtSpark/core/context_compaction.py +241 -226
  10. dtSpark/daemon/__init__.py +36 -22
  11. dtSpark/daemon/action_monitor.py +46 -17
  12. dtSpark/daemon/daemon_app.py +126 -104
  13. dtSpark/daemon/daemon_manager.py +59 -23
  14. dtSpark/daemon/pid_file.py +3 -2
  15. dtSpark/database/autonomous_actions.py +3 -0
  16. dtSpark/database/credential_prompt.py +52 -54
  17. dtSpark/files/manager.py +6 -12
  18. dtSpark/limits/__init__.py +1 -1
  19. dtSpark/limits/tokens.py +2 -2
  20. dtSpark/llm/anthropic_direct.py +246 -141
  21. dtSpark/llm/ollama.py +3 -1
  22. dtSpark/mcp_integration/manager.py +4 -4
  23. dtSpark/mcp_integration/tool_selector.py +83 -77
  24. dtSpark/resources/config.yaml.template +11 -0
  25. dtSpark/safety/patterns.py +45 -46
  26. dtSpark/safety/prompt_inspector.py +8 -1
  27. dtSpark/scheduler/creation_tools.py +273 -181
  28. dtSpark/scheduler/executor.py +503 -221
  29. dtSpark/tools/builtin.py +70 -53
  30. dtSpark/web/endpoints/autonomous_actions.py +12 -9
  31. dtSpark/web/endpoints/chat.py +8 -6
  32. dtSpark/web/endpoints/conversations.py +18 -9
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +70 -5
  36. dtSpark/web/ssl_utils.py +3 -3
  37. dtSpark/web/static/css/dark-theme.css +8 -29
  38. dtSpark/web/static/js/chat.js +6 -8
  39. dtSpark/web/static/js/main.js +8 -8
  40. dtSpark/web/static/js/sse-client.js +130 -122
  41. dtSpark/web/templates/actions.html +5 -5
  42. dtSpark/web/templates/base.html +15 -0
  43. dtSpark/web/templates/chat.html +10 -10
  44. dtSpark/web/templates/conversations.html +6 -2
  45. dtSpark/web/templates/goodbye.html +2 -2
  46. dtSpark/web/templates/main_menu.html +19 -17
  47. dtSpark/web/web_interface.py +2 -2
  48. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/METADATA +9 -2
  49. dtspark-1.1.0a7.dist-info/RECORD +96 -0
  50. dtspark-1.1.0a3.dist-info/RECORD +0 -96
  51. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/WHEEL +0 -0
  52. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/entry_points.txt +0 -0
  53. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/licenses/LICENSE +0 -0
  54. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/top_level.txt +0 -0
@@ -84,6 +84,16 @@ def copy_to_clipboard(text: str) -> bool:
84
84
  logging.error(f"Failed to copy to clipboard: {e}")
85
85
  return False
86
86
 
87
+ # Common string constants (SonarCloud S1192)
88
+ _SETTING_BEDROCK_COST_TRACKING = 'llm_providers.aws_bedrock.cost_tracking.enabled'
89
+ _SETTING_COST_TRACKING = 'aws.cost_tracking.enabled'
90
+ _SETTING_OLLAMA_ENABLED = 'llm_providers.ollama.enabled'
91
+ _DEFAULT_RUNNING_DIR = "./running"
92
+ _PROMPT_ACCESS_MODE = "Select access mode"
93
+ _MSG_NO_MODELS = "No models available"
94
+ _MSG_MANAGED_CONVERSATION = "This conversation is managed by configuration"
95
+ _MSG_DELETE_CANCELLED = "Delete operation cancelled"
96
+
87
97
  class AWSBedrockCLI(AbstractApp):
88
98
  def __init__(self):
89
99
  super().__init__(short_name=agent_type(), full_name=full_name(), version=version(),
@@ -371,10 +381,12 @@ class AWSBedrockCLI(AbstractApp):
371
381
  aws_session_token = self.settings.get('aws.session_token', None)
372
382
 
373
383
  # Configure CLI cost tracking display
374
- cost_tracking_enabled = self._get_nested_setting('llm_providers.aws_bedrock.cost_tracking.enabled', None)
384
+ cost_tracking_enabled = self._get_nested_setting(_SETTING_BEDROCK_COST_TRACKING, None)
375
385
  if cost_tracking_enabled is None:
376
- cost_tracking_enabled = self.settings.get('aws.cost_tracking.enabled', False)
386
+ cost_tracking_enabled = self.settings.get(_SETTING_COST_TRACKING, False)
377
387
  self.cli.cost_tracking_enabled = cost_tracking_enabled
388
+ self.cli.actions_enabled = self._get_nested_setting('autonomous_actions.enabled', False)
389
+ self.cli.new_conversations_allowed = self._get_nested_setting('predefined_conversations.allow_new_conversations', True)
378
390
 
379
391
  progress.update(task_config, advance=100)
380
392
 
@@ -391,7 +403,7 @@ class AWSBedrockCLI(AbstractApp):
391
403
  # Handle missing or various value types
392
404
  if aws_enabled_raw is None:
393
405
  # Setting not found via any method - check if other providers are enabled
394
- ollama_check = self._get_nested_setting('llm_providers.ollama.enabled', False)
406
+ ollama_check = self._get_nested_setting(_SETTING_OLLAMA_ENABLED, False)
395
407
  anthropic_check = self._get_nested_setting('llm_providers.anthropic.enabled', False)
396
408
  if ollama_check or anthropic_check:
397
409
  # Other providers configured, don't default to AWS
@@ -432,7 +444,7 @@ class AWSBedrockCLI(AbstractApp):
432
444
  self.cli.print_warning("Failed to authenticate with AWS Bedrock")
433
445
 
434
446
  # Check if Ollama is available as fallback
435
- ollama_enabled = self._get_nested_setting('llm_providers.ollama.enabled', False)
447
+ ollama_enabled = self._get_nested_setting(_SETTING_OLLAMA_ENABLED, False)
436
448
  if not ollama_enabled:
437
449
  self.cli.print_error("AWS authentication required (Ollama not configured)")
438
450
  self.cli.print_info(f"Run: aws sso login --profile {aws_profile}")
@@ -457,7 +469,7 @@ class AWSBedrockCLI(AbstractApp):
457
469
  progress.update(task_llm, advance=20, description="[cyan]Checking Ollama...")
458
470
 
459
471
  # Check Ollama configuration
460
- ollama_enabled = self._get_nested_setting('llm_providers.ollama.enabled', False)
472
+ ollama_enabled = self._get_nested_setting(_SETTING_OLLAMA_ENABLED, False)
461
473
  if ollama_enabled:
462
474
  try:
463
475
  ollama_url = self._get_nested_setting(
@@ -529,9 +541,9 @@ class AWSBedrockCLI(AbstractApp):
529
541
 
530
542
  # Task 3.5: Retrieve Bedrock cost information (silently, display later)
531
543
  # Only if cost tracking is enabled in configuration
532
- cost_tracking_enabled = self._get_nested_setting('llm_providers.aws_bedrock.cost_tracking.enabled', None)
544
+ cost_tracking_enabled = self._get_nested_setting(_SETTING_BEDROCK_COST_TRACKING, None)
533
545
  if cost_tracking_enabled is None:
534
- cost_tracking_enabled = self.settings.get('aws.cost_tracking.enabled', False)
546
+ cost_tracking_enabled = self.settings.get(_SETTING_COST_TRACKING, False)
535
547
  if cost_tracking_enabled and self.authenticator:
536
548
  task_costs = progress.add_task("[cyan]Retrieving usage costs...", total=100)
537
549
  self.bedrock_costs = None
@@ -796,77 +808,88 @@ class AWSBedrockCLI(AbstractApp):
796
808
  )
797
809
  progress.update(task_conv, advance=100)
798
810
 
799
- # Task 7: Initialise autonomous action scheduler
811
+ # Task 7: Initialise autonomous action scheduler (if enabled)
812
+ self.actions_enabled = self._get_nested_setting('autonomous_actions.enabled', False)
813
+ self.new_conversations_allowed = self._get_nested_setting('predefined_conversations.allow_new_conversations', True)
800
814
  task_scheduler = progress.add_task("[cyan]Initialising action scheduler...", total=100)
801
- try:
802
- from dtSpark.scheduler import (
803
- ActionSchedulerManager,
804
- ActionExecutionQueue,
805
- ActionExecutor
806
- )
807
815
 
808
- # Get database path for scheduler job store
809
- db_path = self.database.db_path or ':memory:'
816
+ if not self.actions_enabled:
817
+ logging.info("Autonomous actions are disabled via configuration")
818
+ self.action_scheduler = None
819
+ self.execution_queue = None
820
+ self.action_executor = None
821
+ self.daemon_is_running = False
822
+ progress.update(task_scheduler, advance=100)
823
+ else:
824
+ try:
825
+ from dtSpark.scheduler import (
826
+ ActionSchedulerManager,
827
+ ActionExecutionQueue,
828
+ ActionExecutor
829
+ )
810
830
 
811
- # Create executor with LLM manager and optional MCP manager
812
- get_tools_func = None
813
- if self.mcp_manager:
814
- def get_tools_func():
815
- import asyncio
816
- loop = getattr(self.mcp_manager, '_initialization_loop', None)
817
- if loop and not loop.is_closed():
818
- return loop.run_until_complete(self.mcp_manager.list_all_tools())
819
- return []
820
-
821
- self.action_executor = ActionExecutor(
822
- database=self.database,
823
- llm_manager=self.llm_manager,
824
- mcp_manager=self.mcp_manager,
825
- get_tools_func=get_tools_func,
826
- config=config_for_manager
827
- )
831
+ # Get database path for scheduler job store
832
+ db_path = self.database.db_path or ':memory:'
828
833
 
829
- # Create execution queue
830
- self.execution_queue = ActionExecutionQueue(
831
- executor_func=self.action_executor.execute
832
- )
834
+ # Create executor with LLM manager and optional MCP manager
835
+ get_tools_func = None
836
+ if self.mcp_manager:
837
+ def get_tools_func():
838
+ import asyncio
839
+ loop = getattr(self.mcp_manager, '_initialization_loop', None)
840
+ if loop and not loop.is_closed():
841
+ return loop.run_until_complete(self.mcp_manager.list_all_tools())
842
+ return []
843
+
844
+ self.action_executor = ActionExecutor(
845
+ database=self.database,
846
+ llm_manager=self.llm_manager,
847
+ mcp_manager=self.mcp_manager,
848
+ get_tools_func=get_tools_func,
849
+ config=config_for_manager
850
+ )
833
851
 
834
- # Create scheduler manager
835
- self.action_scheduler = ActionSchedulerManager(
836
- db_path=db_path,
837
- execution_callback=lambda action_id, user_guid: self.execution_queue.enqueue(
838
- action_id, user_guid, is_manual=False
852
+ # Create execution queue
853
+ self.execution_queue = ActionExecutionQueue(
854
+ executor_func=self.action_executor.execute
839
855
  )
840
- )
841
856
 
842
- # Check if daemon is running (for warning display later)
843
- self.daemon_is_running = self._check_daemon_running()
857
+ # Create scheduler manager
858
+ self.action_scheduler = ActionSchedulerManager(
859
+ db_path=db_path,
860
+ execution_callback=lambda action_id, user_guid: self.execution_queue.enqueue(
861
+ action_id, user_guid, is_manual=False
862
+ )
863
+ )
844
864
 
845
- # Initialise execution components (for manual "Run Now" from UI)
846
- # Note: Scheduled execution is ONLY handled by the daemon process
847
- self.action_scheduler.initialise()
848
- self.execution_queue.start()
865
+ # Check if daemon is running (for warning display later)
866
+ self.daemon_is_running = self._check_daemon_running()
849
867
 
850
- # UI never starts the scheduler - daemon handles all scheduled execution
851
- if self.daemon_is_running:
852
- logging.info("Daemon is running - scheduled actions will be executed by daemon")
853
- else:
854
- logging.warning("Daemon is not running - scheduled actions will NOT execute until daemon is started")
868
+ # Initialise execution components (for manual "Run Now" from UI)
869
+ # Note: Scheduled execution is ONLY handled by the daemon process
870
+ self.action_scheduler.initialise()
871
+ self.execution_queue.start()
855
872
 
856
- progress.update(task_scheduler, advance=100)
873
+ # UI never starts the scheduler - daemon handles all scheduled execution
874
+ if self.daemon_is_running:
875
+ logging.info("Daemon is running - scheduled actions will be executed by daemon")
876
+ else:
877
+ logging.warning("Daemon is not running - scheduled actions will NOT execute until daemon is started")
857
878
 
858
- except ImportError as e:
859
- logging.warning(f"Action scheduler not available (APScheduler not installed): {e}")
860
- self.action_scheduler = None
861
- self.execution_queue = None
862
- self.action_executor = None
863
- progress.update(task_scheduler, advance=100)
864
- except Exception as e:
865
- logging.error(f"Failed to initialise action scheduler: {e}")
866
- self.action_scheduler = None
867
- self.execution_queue = None
868
- self.action_executor = None
869
- progress.update(task_scheduler, advance=100)
879
+ progress.update(task_scheduler, advance=100)
880
+
881
+ except ImportError as e:
882
+ logging.warning(f"Action scheduler not available (APScheduler not installed): {e}")
883
+ self.action_scheduler = None
884
+ self.execution_queue = None
885
+ self.action_executor = None
886
+ progress.update(task_scheduler, advance=100)
887
+ except Exception as e:
888
+ logging.error(f"Failed to initialise action scheduler: {e}")
889
+ self.action_scheduler = None
890
+ self.execution_queue = None
891
+ self.action_executor = None
892
+ progress.update(task_scheduler, advance=100)
870
893
 
871
894
  # Display application info first (user identification)
872
895
  self.cli.display_application_info(self.user_guid)
@@ -881,8 +904,8 @@ class AWSBedrockCLI(AbstractApp):
881
904
  if mcp_enabled and self.mcp_manager:
882
905
  self.cli.display_mcp_status(self.mcp_manager)
883
906
 
884
- # Display daemon status warning if daemon is not running
885
- if hasattr(self, 'daemon_is_running') and not self.daemon_is_running:
907
+ # Display daemon status warning if daemon is not running (only when actions are enabled)
908
+ if self.actions_enabled and hasattr(self, 'daemon_is_running') and not self.daemon_is_running:
886
909
  # Check if there are any scheduled actions
887
910
  try:
888
911
  actions = self.database.get_all_actions(include_disabled=False)
@@ -1638,7 +1661,7 @@ class AWSBedrockCLI(AbstractApp):
1638
1661
  enable_filesystem_tools = Confirm.ask("Enable embedded filesystem tools?", default=False)
1639
1662
 
1640
1663
  # Default values
1641
- filesystem_allowed_path = "./running"
1664
+ filesystem_allowed_path = _DEFAULT_RUNNING_DIR
1642
1665
  filesystem_access_mode = "read_write"
1643
1666
 
1644
1667
  if enable_filesystem_tools:
@@ -1648,7 +1671,7 @@ class AWSBedrockCLI(AbstractApp):
1648
1671
 
1649
1672
  filesystem_allowed_path = Prompt.ask(
1650
1673
  "Allowed directory path (tools can only access files within this directory)",
1651
- default="./running"
1674
+ default=_DEFAULT_RUNNING_DIR
1652
1675
  )
1653
1676
 
1654
1677
  # Access mode
@@ -1661,7 +1684,7 @@ class AWSBedrockCLI(AbstractApp):
1661
1684
  cli.console.print(" [2] Read/Write - Full access (read + write files, create directories)")
1662
1685
  cli.console.print()
1663
1686
  access_mode_choice = Prompt.ask(
1664
- "Select access mode",
1687
+ _PROMPT_ACCESS_MODE,
1665
1688
  choices=["1", "2"],
1666
1689
  default="2"
1667
1690
  )
@@ -1674,7 +1697,7 @@ class AWSBedrockCLI(AbstractApp):
1674
1697
  enable_document_tools = Confirm.ask("Enable embedded document tools (MS Office & PDF)?", default=False)
1675
1698
 
1676
1699
  # Default values
1677
- document_allowed_path = "./running"
1700
+ document_allowed_path = _DEFAULT_RUNNING_DIR
1678
1701
  document_access_mode = "read"
1679
1702
  document_max_file_size = "50"
1680
1703
  document_max_pdf_pages = "100"
@@ -1689,7 +1712,7 @@ class AWSBedrockCLI(AbstractApp):
1689
1712
 
1690
1713
  document_allowed_path = Prompt.ask(
1691
1714
  "Allowed directory path for documents",
1692
- default="./running"
1715
+ default=_DEFAULT_RUNNING_DIR
1693
1716
  )
1694
1717
 
1695
1718
  # Access mode
@@ -1702,7 +1725,7 @@ class AWSBedrockCLI(AbstractApp):
1702
1725
  cli.console.print(" [2] Read/Write - Read and create documents")
1703
1726
  cli.console.print()
1704
1727
  doc_access_mode_choice = Prompt.ask(
1705
- "Select access mode",
1728
+ _PROMPT_ACCESS_MODE,
1706
1729
  choices=["1", "2"],
1707
1730
  default="1"
1708
1731
  )
@@ -1742,7 +1765,7 @@ class AWSBedrockCLI(AbstractApp):
1742
1765
  enable_archive_tools = Confirm.ask("Enable embedded archive tools (ZIP, TAR)?", default=False)
1743
1766
 
1744
1767
  # Default values
1745
- archive_allowed_path = "./running"
1768
+ archive_allowed_path = _DEFAULT_RUNNING_DIR
1746
1769
  archive_access_mode = "read"
1747
1770
  archive_max_file_size = "100"
1748
1771
  archive_max_files_to_list = "1000"
@@ -1754,7 +1777,7 @@ class AWSBedrockCLI(AbstractApp):
1754
1777
 
1755
1778
  archive_allowed_path = Prompt.ask(
1756
1779
  "Allowed directory path for archives",
1757
- default="./running"
1780
+ default=_DEFAULT_RUNNING_DIR
1758
1781
  )
1759
1782
 
1760
1783
  # Access mode
@@ -1767,7 +1790,7 @@ class AWSBedrockCLI(AbstractApp):
1767
1790
  cli.console.print(" [2] Read/Write - Read and extract archives to disk")
1768
1791
  cli.console.print()
1769
1792
  archive_access_mode_choice = Prompt.ask(
1770
- "Select access mode",
1793
+ _PROMPT_ACCESS_MODE,
1771
1794
  choices=["1", "2"],
1772
1795
  default="1"
1773
1796
  )
@@ -2211,14 +2234,14 @@ class AWSBedrockCLI(AbstractApp):
2211
2234
  if document_templates_path:
2212
2235
  escaped_templates_path = document_templates_path.replace('\\', '/')
2213
2236
  config_content = re.sub(
2214
- r'(templates_path:\s+)(null|[^\s#]+)',
2237
+ r'(templates_path:\s+)([^\s#]+)',
2215
2238
  f'\\g<1>{escaped_templates_path}',
2216
2239
  config_content
2217
2240
  )
2218
2241
  # Default author (if provided)
2219
2242
  if document_default_author:
2220
2243
  config_content = re.sub(
2221
- r'(default_author:\s+)(null|[^\s#]+)',
2244
+ r'(default_author:\s+)([^\s#]+)',
2222
2245
  f'\\g<1>{document_default_author}',
2223
2246
  config_content
2224
2247
  )
@@ -2377,9 +2400,9 @@ class AWSBedrockCLI(AbstractApp):
2377
2400
  def regather_and_display_costs(self):
2378
2401
  """Re-gather AWS Bedrock cost information and display it."""
2379
2402
  # Check if cost tracking is enabled (new path with legacy fallback)
2380
- cost_tracking_enabled = self._get_nested_setting('llm_providers.aws_bedrock.cost_tracking.enabled', None)
2403
+ cost_tracking_enabled = self._get_nested_setting(_SETTING_BEDROCK_COST_TRACKING, None)
2381
2404
  if cost_tracking_enabled is None:
2382
- cost_tracking_enabled = self.settings.get('aws.cost_tracking.enabled', False)
2405
+ cost_tracking_enabled = self.settings.get(_SETTING_COST_TRACKING, False)
2383
2406
  if not cost_tracking_enabled:
2384
2407
  self.cli.print_warning("Cost tracking is disabled in configuration")
2385
2408
  return
@@ -2748,7 +2771,7 @@ class AWSBedrockCLI(AbstractApp):
2748
2771
  # Step 1: Select model (will be used for both creation and execution)
2749
2772
  models = self.llm_manager.list_all_models()
2750
2773
  if not models:
2751
- self.cli.print_error("No models available")
2774
+ self.cli.print_error(_MSG_NO_MODELS)
2752
2775
  return
2753
2776
 
2754
2777
  self.cli.console.print("\n[bold cyan]Select Model[/bold cyan]")
@@ -2942,7 +2965,7 @@ class AWSBedrockCLI(AbstractApp):
2942
2965
  progress.update(task, advance=100)
2943
2966
 
2944
2967
  if not models:
2945
- self.cli.print_error("No models available")
2968
+ self.cli.print_error(_MSG_NO_MODELS)
2946
2969
  return None
2947
2970
 
2948
2971
  model_id = self.cli.display_models(models)
@@ -3223,7 +3246,7 @@ class AWSBedrockCLI(AbstractApp):
3223
3246
  # Check if conversation is predefined - if so, block file deletion
3224
3247
  if self.database.is_conversation_predefined(self.conversation_manager.current_conversation_id):
3225
3248
  self.cli.print_error("Cannot delete files from predefined conversations")
3226
- self.cli.print_info("This conversation is managed by configuration")
3249
+ self.cli.print_info(_MSG_MANAGED_CONVERSATION)
3227
3250
  self.cli.wait_for_enter()
3228
3251
  continue
3229
3252
 
@@ -3247,7 +3270,7 @@ class AWSBedrockCLI(AbstractApp):
3247
3270
  file_ids_input = self.cli.get_input("File IDs to delete").strip()
3248
3271
 
3249
3272
  if not file_ids_input:
3250
- self.cli.print_info("Delete operation cancelled")
3273
+ self.cli.print_info(_MSG_DELETE_CANCELLED)
3251
3274
  self.cli.wait_for_enter()
3252
3275
  continue
3253
3276
 
@@ -3267,7 +3290,7 @@ class AWSBedrockCLI(AbstractApp):
3267
3290
  else:
3268
3291
  self.cli.print_error("Failed to delete files")
3269
3292
  else:
3270
- self.cli.print_info("Delete operation cancelled")
3293
+ self.cli.print_info(_MSG_DELETE_CANCELLED)
3271
3294
  else:
3272
3295
  # Parse comma-separated IDs
3273
3296
  try:
@@ -3297,7 +3320,7 @@ class AWSBedrockCLI(AbstractApp):
3297
3320
  if failed_ids:
3298
3321
  self.cli.print_error(f"Failed to delete files with IDs: {', '.join(map(str, failed_ids))}")
3299
3322
  else:
3300
- self.cli.print_info("Delete operation cancelled")
3323
+ self.cli.print_info(_MSG_DELETE_CANCELLED)
3301
3324
 
3302
3325
  except ValueError:
3303
3326
  self.cli.print_error("Invalid file IDs. Please enter comma-separated numbers or 'all'")
@@ -3309,7 +3332,7 @@ class AWSBedrockCLI(AbstractApp):
3309
3332
  # Check if conversation is predefined - if so, block model changes
3310
3333
  if self.database.is_conversation_predefined(self.conversation_manager.current_conversation_id):
3311
3334
  self.cli.print_error("Cannot change model for predefined conversations")
3312
- self.cli.print_info("This conversation is managed by configuration")
3335
+ self.cli.print_info(_MSG_MANAGED_CONVERSATION)
3313
3336
  self.cli.wait_for_enter()
3314
3337
  continue
3315
3338
 
@@ -3327,7 +3350,7 @@ class AWSBedrockCLI(AbstractApp):
3327
3350
  progress.update(task, advance=100)
3328
3351
 
3329
3352
  if not models:
3330
- self.cli.print_error("No models available")
3353
+ self.cli.print_error(_MSG_NO_MODELS)
3331
3354
  self.cli.wait_for_enter()
3332
3355
  continue
3333
3356
 
@@ -3374,7 +3397,7 @@ class AWSBedrockCLI(AbstractApp):
3374
3397
  # Check if conversation is predefined - if so, block instruction changes
3375
3398
  if self.database.is_conversation_predefined(self.conversation_manager.current_conversation_id):
3376
3399
  self.cli.print_error("Cannot change instructions for predefined conversations")
3377
- self.cli.print_info("This conversation is managed by configuration")
3400
+ self.cli.print_info(_MSG_MANAGED_CONVERSATION)
3378
3401
  self.cli.wait_for_enter()
3379
3402
  continue
3380
3403