dtSpark 1.1.0a3__py3-none-any.whl → 1.1.0a6__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 +69 -62
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +112 -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 +10 -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 +11 -9
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +65 -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 +13 -0
  43. dtSpark/web/templates/chat.html +10 -10
  44. dtSpark/web/templates/conversations.html +2 -2
  45. dtSpark/web/templates/goodbye.html +2 -2
  46. dtSpark/web/templates/main_menu.html +17 -17
  47. dtSpark/web/web_interface.py +2 -2
  48. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/METADATA +9 -2
  49. dtspark-1.1.0a6.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.0a6.dist-info}/WHEEL +0 -0
  52. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/entry_points.txt +0 -0
  53. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/licenses/LICENSE +0 -0
  54. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.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,11 @@ 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)
378
389
 
379
390
  progress.update(task_config, advance=100)
380
391
 
@@ -391,7 +402,7 @@ class AWSBedrockCLI(AbstractApp):
391
402
  # Handle missing or various value types
392
403
  if aws_enabled_raw is None:
393
404
  # Setting not found via any method - check if other providers are enabled
394
- ollama_check = self._get_nested_setting('llm_providers.ollama.enabled', False)
405
+ ollama_check = self._get_nested_setting(_SETTING_OLLAMA_ENABLED, False)
395
406
  anthropic_check = self._get_nested_setting('llm_providers.anthropic.enabled', False)
396
407
  if ollama_check or anthropic_check:
397
408
  # Other providers configured, don't default to AWS
@@ -432,7 +443,7 @@ class AWSBedrockCLI(AbstractApp):
432
443
  self.cli.print_warning("Failed to authenticate with AWS Bedrock")
433
444
 
434
445
  # Check if Ollama is available as fallback
435
- ollama_enabled = self._get_nested_setting('llm_providers.ollama.enabled', False)
446
+ ollama_enabled = self._get_nested_setting(_SETTING_OLLAMA_ENABLED, False)
436
447
  if not ollama_enabled:
437
448
  self.cli.print_error("AWS authentication required (Ollama not configured)")
438
449
  self.cli.print_info(f"Run: aws sso login --profile {aws_profile}")
@@ -457,7 +468,7 @@ class AWSBedrockCLI(AbstractApp):
457
468
  progress.update(task_llm, advance=20, description="[cyan]Checking Ollama...")
458
469
 
459
470
  # Check Ollama configuration
460
- ollama_enabled = self._get_nested_setting('llm_providers.ollama.enabled', False)
471
+ ollama_enabled = self._get_nested_setting(_SETTING_OLLAMA_ENABLED, False)
461
472
  if ollama_enabled:
462
473
  try:
463
474
  ollama_url = self._get_nested_setting(
@@ -529,9 +540,9 @@ class AWSBedrockCLI(AbstractApp):
529
540
 
530
541
  # Task 3.5: Retrieve Bedrock cost information (silently, display later)
531
542
  # Only if cost tracking is enabled in configuration
532
- cost_tracking_enabled = self._get_nested_setting('llm_providers.aws_bedrock.cost_tracking.enabled', None)
543
+ cost_tracking_enabled = self._get_nested_setting(_SETTING_BEDROCK_COST_TRACKING, None)
533
544
  if cost_tracking_enabled is None:
534
- cost_tracking_enabled = self.settings.get('aws.cost_tracking.enabled', False)
545
+ cost_tracking_enabled = self.settings.get(_SETTING_COST_TRACKING, False)
535
546
  if cost_tracking_enabled and self.authenticator:
536
547
  task_costs = progress.add_task("[cyan]Retrieving usage costs...", total=100)
537
548
  self.bedrock_costs = None
@@ -796,77 +807,87 @@ class AWSBedrockCLI(AbstractApp):
796
807
  )
797
808
  progress.update(task_conv, advance=100)
798
809
 
799
- # Task 7: Initialise autonomous action scheduler
810
+ # Task 7: Initialise autonomous action scheduler (if enabled)
811
+ self.actions_enabled = self._get_nested_setting('autonomous_actions.enabled', False)
800
812
  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
813
 
808
- # Get database path for scheduler job store
809
- db_path = self.database.db_path or ':memory:'
814
+ if not self.actions_enabled:
815
+ logging.info("Autonomous actions are disabled via configuration")
816
+ self.action_scheduler = None
817
+ self.execution_queue = None
818
+ self.action_executor = None
819
+ self.daemon_is_running = False
820
+ progress.update(task_scheduler, advance=100)
821
+ else:
822
+ try:
823
+ from dtSpark.scheduler import (
824
+ ActionSchedulerManager,
825
+ ActionExecutionQueue,
826
+ ActionExecutor
827
+ )
810
828
 
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
- )
829
+ # Get database path for scheduler job store
830
+ db_path = self.database.db_path or ':memory:'
828
831
 
829
- # Create execution queue
830
- self.execution_queue = ActionExecutionQueue(
831
- executor_func=self.action_executor.execute
832
- )
832
+ # Create executor with LLM manager and optional MCP manager
833
+ get_tools_func = None
834
+ if self.mcp_manager:
835
+ def get_tools_func():
836
+ import asyncio
837
+ loop = getattr(self.mcp_manager, '_initialization_loop', None)
838
+ if loop and not loop.is_closed():
839
+ return loop.run_until_complete(self.mcp_manager.list_all_tools())
840
+ return []
841
+
842
+ self.action_executor = ActionExecutor(
843
+ database=self.database,
844
+ llm_manager=self.llm_manager,
845
+ mcp_manager=self.mcp_manager,
846
+ get_tools_func=get_tools_func,
847
+ config=config_for_manager
848
+ )
833
849
 
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
850
+ # Create execution queue
851
+ self.execution_queue = ActionExecutionQueue(
852
+ executor_func=self.action_executor.execute
839
853
  )
840
- )
841
854
 
842
- # Check if daemon is running (for warning display later)
843
- self.daemon_is_running = self._check_daemon_running()
855
+ # Create scheduler manager
856
+ self.action_scheduler = ActionSchedulerManager(
857
+ db_path=db_path,
858
+ execution_callback=lambda action_id, user_guid: self.execution_queue.enqueue(
859
+ action_id, user_guid, is_manual=False
860
+ )
861
+ )
844
862
 
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()
863
+ # Check if daemon is running (for warning display later)
864
+ self.daemon_is_running = self._check_daemon_running()
849
865
 
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")
866
+ # Initialise execution components (for manual "Run Now" from UI)
867
+ # Note: Scheduled execution is ONLY handled by the daemon process
868
+ self.action_scheduler.initialise()
869
+ self.execution_queue.start()
855
870
 
856
- progress.update(task_scheduler, advance=100)
871
+ # UI never starts the scheduler - daemon handles all scheduled execution
872
+ if self.daemon_is_running:
873
+ logging.info("Daemon is running - scheduled actions will be executed by daemon")
874
+ else:
875
+ logging.warning("Daemon is not running - scheduled actions will NOT execute until daemon is started")
857
876
 
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)
877
+ progress.update(task_scheduler, advance=100)
878
+
879
+ except ImportError as e:
880
+ logging.warning(f"Action scheduler not available (APScheduler not installed): {e}")
881
+ self.action_scheduler = None
882
+ self.execution_queue = None
883
+ self.action_executor = None
884
+ progress.update(task_scheduler, advance=100)
885
+ except Exception as e:
886
+ logging.error(f"Failed to initialise action scheduler: {e}")
887
+ self.action_scheduler = None
888
+ self.execution_queue = None
889
+ self.action_executor = None
890
+ progress.update(task_scheduler, advance=100)
870
891
 
871
892
  # Display application info first (user identification)
872
893
  self.cli.display_application_info(self.user_guid)
@@ -881,8 +902,8 @@ class AWSBedrockCLI(AbstractApp):
881
902
  if mcp_enabled and self.mcp_manager:
882
903
  self.cli.display_mcp_status(self.mcp_manager)
883
904
 
884
- # Display daemon status warning if daemon is not running
885
- if hasattr(self, 'daemon_is_running') and not self.daemon_is_running:
905
+ # Display daemon status warning if daemon is not running (only when actions are enabled)
906
+ if self.actions_enabled and hasattr(self, 'daemon_is_running') and not self.daemon_is_running:
886
907
  # Check if there are any scheduled actions
887
908
  try:
888
909
  actions = self.database.get_all_actions(include_disabled=False)
@@ -1638,7 +1659,7 @@ class AWSBedrockCLI(AbstractApp):
1638
1659
  enable_filesystem_tools = Confirm.ask("Enable embedded filesystem tools?", default=False)
1639
1660
 
1640
1661
  # Default values
1641
- filesystem_allowed_path = "./running"
1662
+ filesystem_allowed_path = _DEFAULT_RUNNING_DIR
1642
1663
  filesystem_access_mode = "read_write"
1643
1664
 
1644
1665
  if enable_filesystem_tools:
@@ -1648,7 +1669,7 @@ class AWSBedrockCLI(AbstractApp):
1648
1669
 
1649
1670
  filesystem_allowed_path = Prompt.ask(
1650
1671
  "Allowed directory path (tools can only access files within this directory)",
1651
- default="./running"
1672
+ default=_DEFAULT_RUNNING_DIR
1652
1673
  )
1653
1674
 
1654
1675
  # Access mode
@@ -1661,7 +1682,7 @@ class AWSBedrockCLI(AbstractApp):
1661
1682
  cli.console.print(" [2] Read/Write - Full access (read + write files, create directories)")
1662
1683
  cli.console.print()
1663
1684
  access_mode_choice = Prompt.ask(
1664
- "Select access mode",
1685
+ _PROMPT_ACCESS_MODE,
1665
1686
  choices=["1", "2"],
1666
1687
  default="2"
1667
1688
  )
@@ -1674,7 +1695,7 @@ class AWSBedrockCLI(AbstractApp):
1674
1695
  enable_document_tools = Confirm.ask("Enable embedded document tools (MS Office & PDF)?", default=False)
1675
1696
 
1676
1697
  # Default values
1677
- document_allowed_path = "./running"
1698
+ document_allowed_path = _DEFAULT_RUNNING_DIR
1678
1699
  document_access_mode = "read"
1679
1700
  document_max_file_size = "50"
1680
1701
  document_max_pdf_pages = "100"
@@ -1689,7 +1710,7 @@ class AWSBedrockCLI(AbstractApp):
1689
1710
 
1690
1711
  document_allowed_path = Prompt.ask(
1691
1712
  "Allowed directory path for documents",
1692
- default="./running"
1713
+ default=_DEFAULT_RUNNING_DIR
1693
1714
  )
1694
1715
 
1695
1716
  # Access mode
@@ -1702,7 +1723,7 @@ class AWSBedrockCLI(AbstractApp):
1702
1723
  cli.console.print(" [2] Read/Write - Read and create documents")
1703
1724
  cli.console.print()
1704
1725
  doc_access_mode_choice = Prompt.ask(
1705
- "Select access mode",
1726
+ _PROMPT_ACCESS_MODE,
1706
1727
  choices=["1", "2"],
1707
1728
  default="1"
1708
1729
  )
@@ -1742,7 +1763,7 @@ class AWSBedrockCLI(AbstractApp):
1742
1763
  enable_archive_tools = Confirm.ask("Enable embedded archive tools (ZIP, TAR)?", default=False)
1743
1764
 
1744
1765
  # Default values
1745
- archive_allowed_path = "./running"
1766
+ archive_allowed_path = _DEFAULT_RUNNING_DIR
1746
1767
  archive_access_mode = "read"
1747
1768
  archive_max_file_size = "100"
1748
1769
  archive_max_files_to_list = "1000"
@@ -1754,7 +1775,7 @@ class AWSBedrockCLI(AbstractApp):
1754
1775
 
1755
1776
  archive_allowed_path = Prompt.ask(
1756
1777
  "Allowed directory path for archives",
1757
- default="./running"
1778
+ default=_DEFAULT_RUNNING_DIR
1758
1779
  )
1759
1780
 
1760
1781
  # Access mode
@@ -1767,7 +1788,7 @@ class AWSBedrockCLI(AbstractApp):
1767
1788
  cli.console.print(" [2] Read/Write - Read and extract archives to disk")
1768
1789
  cli.console.print()
1769
1790
  archive_access_mode_choice = Prompt.ask(
1770
- "Select access mode",
1791
+ _PROMPT_ACCESS_MODE,
1771
1792
  choices=["1", "2"],
1772
1793
  default="1"
1773
1794
  )
@@ -2211,14 +2232,14 @@ class AWSBedrockCLI(AbstractApp):
2211
2232
  if document_templates_path:
2212
2233
  escaped_templates_path = document_templates_path.replace('\\', '/')
2213
2234
  config_content = re.sub(
2214
- r'(templates_path:\s+)(null|[^\s#]+)',
2235
+ r'(templates_path:\s+)([^\s#]+)',
2215
2236
  f'\\g<1>{escaped_templates_path}',
2216
2237
  config_content
2217
2238
  )
2218
2239
  # Default author (if provided)
2219
2240
  if document_default_author:
2220
2241
  config_content = re.sub(
2221
- r'(default_author:\s+)(null|[^\s#]+)',
2242
+ r'(default_author:\s+)([^\s#]+)',
2222
2243
  f'\\g<1>{document_default_author}',
2223
2244
  config_content
2224
2245
  )
@@ -2377,9 +2398,9 @@ class AWSBedrockCLI(AbstractApp):
2377
2398
  def regather_and_display_costs(self):
2378
2399
  """Re-gather AWS Bedrock cost information and display it."""
2379
2400
  # 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)
2401
+ cost_tracking_enabled = self._get_nested_setting(_SETTING_BEDROCK_COST_TRACKING, None)
2381
2402
  if cost_tracking_enabled is None:
2382
- cost_tracking_enabled = self.settings.get('aws.cost_tracking.enabled', False)
2403
+ cost_tracking_enabled = self.settings.get(_SETTING_COST_TRACKING, False)
2383
2404
  if not cost_tracking_enabled:
2384
2405
  self.cli.print_warning("Cost tracking is disabled in configuration")
2385
2406
  return
@@ -2748,7 +2769,7 @@ class AWSBedrockCLI(AbstractApp):
2748
2769
  # Step 1: Select model (will be used for both creation and execution)
2749
2770
  models = self.llm_manager.list_all_models()
2750
2771
  if not models:
2751
- self.cli.print_error("No models available")
2772
+ self.cli.print_error(_MSG_NO_MODELS)
2752
2773
  return
2753
2774
 
2754
2775
  self.cli.console.print("\n[bold cyan]Select Model[/bold cyan]")
@@ -2942,7 +2963,7 @@ class AWSBedrockCLI(AbstractApp):
2942
2963
  progress.update(task, advance=100)
2943
2964
 
2944
2965
  if not models:
2945
- self.cli.print_error("No models available")
2966
+ self.cli.print_error(_MSG_NO_MODELS)
2946
2967
  return None
2947
2968
 
2948
2969
  model_id = self.cli.display_models(models)
@@ -3223,7 +3244,7 @@ class AWSBedrockCLI(AbstractApp):
3223
3244
  # Check if conversation is predefined - if so, block file deletion
3224
3245
  if self.database.is_conversation_predefined(self.conversation_manager.current_conversation_id):
3225
3246
  self.cli.print_error("Cannot delete files from predefined conversations")
3226
- self.cli.print_info("This conversation is managed by configuration")
3247
+ self.cli.print_info(_MSG_MANAGED_CONVERSATION)
3227
3248
  self.cli.wait_for_enter()
3228
3249
  continue
3229
3250
 
@@ -3247,7 +3268,7 @@ class AWSBedrockCLI(AbstractApp):
3247
3268
  file_ids_input = self.cli.get_input("File IDs to delete").strip()
3248
3269
 
3249
3270
  if not file_ids_input:
3250
- self.cli.print_info("Delete operation cancelled")
3271
+ self.cli.print_info(_MSG_DELETE_CANCELLED)
3251
3272
  self.cli.wait_for_enter()
3252
3273
  continue
3253
3274
 
@@ -3267,7 +3288,7 @@ class AWSBedrockCLI(AbstractApp):
3267
3288
  else:
3268
3289
  self.cli.print_error("Failed to delete files")
3269
3290
  else:
3270
- self.cli.print_info("Delete operation cancelled")
3291
+ self.cli.print_info(_MSG_DELETE_CANCELLED)
3271
3292
  else:
3272
3293
  # Parse comma-separated IDs
3273
3294
  try:
@@ -3297,7 +3318,7 @@ class AWSBedrockCLI(AbstractApp):
3297
3318
  if failed_ids:
3298
3319
  self.cli.print_error(f"Failed to delete files with IDs: {', '.join(map(str, failed_ids))}")
3299
3320
  else:
3300
- self.cli.print_info("Delete operation cancelled")
3321
+ self.cli.print_info(_MSG_DELETE_CANCELLED)
3301
3322
 
3302
3323
  except ValueError:
3303
3324
  self.cli.print_error("Invalid file IDs. Please enter comma-separated numbers or 'all'")
@@ -3309,7 +3330,7 @@ class AWSBedrockCLI(AbstractApp):
3309
3330
  # Check if conversation is predefined - if so, block model changes
3310
3331
  if self.database.is_conversation_predefined(self.conversation_manager.current_conversation_id):
3311
3332
  self.cli.print_error("Cannot change model for predefined conversations")
3312
- self.cli.print_info("This conversation is managed by configuration")
3333
+ self.cli.print_info(_MSG_MANAGED_CONVERSATION)
3313
3334
  self.cli.wait_for_enter()
3314
3335
  continue
3315
3336
 
@@ -3327,7 +3348,7 @@ class AWSBedrockCLI(AbstractApp):
3327
3348
  progress.update(task, advance=100)
3328
3349
 
3329
3350
  if not models:
3330
- self.cli.print_error("No models available")
3351
+ self.cli.print_error(_MSG_NO_MODELS)
3331
3352
  self.cli.wait_for_enter()
3332
3353
  continue
3333
3354
 
@@ -3374,7 +3395,7 @@ class AWSBedrockCLI(AbstractApp):
3374
3395
  # Check if conversation is predefined - if so, block instruction changes
3375
3396
  if self.database.is_conversation_predefined(self.conversation_manager.current_conversation_id):
3376
3397
  self.cli.print_error("Cannot change instructions for predefined conversations")
3377
- self.cli.print_info("This conversation is managed by configuration")
3398
+ self.cli.print_info(_MSG_MANAGED_CONVERSATION)
3378
3399
  self.cli.wait_for_enter()
3379
3400
  continue
3380
3401