omnimancer-cli 0.1.0__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 (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,870 @@
1
+ """
2
+ Agent Persona Architecture for Omnimancer.
3
+
4
+ This module provides the architectural design for mapping configuration templates
5
+ to agent personas with clear interfaces and data models.
6
+ """
7
+
8
+ import logging
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import asdict, dataclass, field
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from typing import Any, Callable, Dict, List, Optional, Set
14
+
15
+ from omnimancer.core.models import ConfigTemplate, ConfigTemplateManager
16
+
17
+ from .config import AgentConfig, ProviderConfig, ProviderType
18
+ from .status_core import AgentStatus
19
+ from .status_manager import UnifiedStatusManager as AgentStatusManager
20
+ from .status_manager import (
21
+ get_status_manager,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class PersonaCapability(Enum):
28
+ """Capabilities that agent personas can have."""
29
+
30
+ TOOL_CALLING = "tool_calling"
31
+ CODE_GENERATION = "code_generation"
32
+ FILE_OPERATIONS = "file_operations"
33
+ WEB_SEARCH = "web_search"
34
+ LARGE_CONTEXT = "large_context"
35
+ RESEARCH = "research"
36
+ CREATIVE_WRITING = "creative_writing"
37
+ HIGH_TEMPERATURE = "high_temperature"
38
+ FAST_RESPONSE = "fast_response"
39
+ COST_EFFICIENT = "cost_efficient"
40
+ BALANCED = "balanced"
41
+ GENERAL_PURPOSE = "general_purpose"
42
+ MULTIMODAL = "multimodal"
43
+ FUNCTION_CALLING = "function_calling"
44
+ REASONING = "reasoning"
45
+ MATH = "math"
46
+ ANALYSIS = "analysis"
47
+
48
+
49
+ class PersonaCategory(Enum):
50
+ """Categories for organizing agent personas."""
51
+
52
+ DEVELOPMENT = "development"
53
+ RESEARCH = "research"
54
+ CREATIVE = "creative"
55
+ PRODUCTIVITY = "productivity"
56
+ ANALYSIS = "analysis"
57
+ GENERAL = "general"
58
+ CUSTOM = "custom"
59
+
60
+
61
+ class PersonaStatus(Enum):
62
+ """Status of agent personas."""
63
+
64
+ AVAILABLE = "available"
65
+ ACTIVE = "active"
66
+ DISABLED = "disabled"
67
+ ERROR = "error"
68
+ LOADING = "loading"
69
+
70
+
71
+ @dataclass
72
+ class PersonaMetadata:
73
+ """Metadata structure for UI display and management."""
74
+
75
+ created_at: datetime = field(default_factory=datetime.now)
76
+ updated_at: datetime = field(default_factory=datetime.now)
77
+ version: str = "1.0.0"
78
+ author: str = "Omnimancer"
79
+ tags: List[str] = field(default_factory=list)
80
+ usage_count: int = 0
81
+ last_used: Optional[datetime] = None
82
+ rating: Optional[float] = None
83
+ user_notes: str = ""
84
+ is_builtin: bool = True
85
+ is_custom: bool = False
86
+
87
+ def update_usage(self) -> None:
88
+ """Update usage statistics."""
89
+ self.usage_count += 1
90
+ self.last_used = datetime.now()
91
+ self.updated_at = datetime.now()
92
+
93
+
94
+ @dataclass
95
+ class PersonaConfiguration:
96
+ """Configuration mapping for agent personas."""
97
+
98
+ template_id: str # Maps to ConfigTemplate
99
+ primary_provider: str # Primary provider from template
100
+ fallback_providers: List[str] = field(default_factory=list) # Fallback providers
101
+
102
+ # Override settings (optional)
103
+ temperature_override: Optional[float] = None
104
+ max_tokens_override: Optional[int] = None
105
+ timeout_override: Optional[int] = None
106
+
107
+ # Omnimancer-specific settings
108
+ tools_enabled: bool = True
109
+ web_search_enabled: bool = False
110
+ file_operations_enabled: bool = True
111
+ approval_required: bool = True
112
+ auto_approve_safe_operations: bool = False
113
+
114
+ def get_template(self, template_manager: ConfigTemplateManager) -> ConfigTemplate:
115
+ """Get the underlying configuration template."""
116
+ return template_manager.get_template(self.template_id)
117
+
118
+ def get_primary_provider_config(
119
+ self, template_manager: ConfigTemplateManager
120
+ ) -> Dict[str, Any]:
121
+ """Get provider configuration for the primary provider."""
122
+ template = self.get_template(template_manager)
123
+ if self.primary_provider not in template.provider_configs:
124
+ raise ValueError(
125
+ f"Provider {self.primary_provider} not found in template {self.template_id}"
126
+ )
127
+
128
+ config = template.provider_configs[self.primary_provider].copy()
129
+
130
+ # Apply overrides
131
+ if self.temperature_override is not None:
132
+ config["temperature"] = self.temperature_override
133
+ if self.max_tokens_override is not None:
134
+ config["max_tokens"] = self.max_tokens_override
135
+ if self.timeout_override is not None:
136
+ config["timeout"] = self.timeout_override
137
+
138
+ return config
139
+
140
+ def to_provider_config(
141
+ self, provider_id: str, template_manager: ConfigTemplateManager
142
+ ) -> ProviderConfig:
143
+ """Convert to ProviderConfig for AgentConfig."""
144
+ template = self.get_template(template_manager)
145
+ provider_config = self.get_primary_provider_config(template_manager)
146
+
147
+ # Map provider name to ProviderType
148
+ provider_type_mapping = {
149
+ "claude": ProviderType.ANTHROPIC,
150
+ "openai": ProviderType.OPENAI,
151
+ "gemini": ProviderType.GOOGLE,
152
+ "perplexity": ProviderType.PERPLEXITY,
153
+ "ollama": ProviderType.OPENROUTER, # Map to OpenRouter as fallback
154
+ "xai": ProviderType.XAI,
155
+ "mistral": ProviderType.MISTRAL,
156
+ "cohere": ProviderType.OPENROUTER, # Map to OpenRouter as fallback
157
+ "claude-code": ProviderType.CLAUDE_CODE,
158
+ }
159
+
160
+ provider_type = provider_type_mapping.get(
161
+ self.primary_provider, ProviderType.ANTHROPIC
162
+ )
163
+ model_id = template.recommended_models.get(self.primary_provider, "default")
164
+
165
+ return ProviderConfig(
166
+ provider_type=provider_type,
167
+ enabled=True,
168
+ model=model_id,
169
+ temperature=provider_config.get("temperature", 0.7),
170
+ max_tokens=provider_config.get("max_tokens", 4096),
171
+ timeout_seconds=provider_config.get("timeout", 30),
172
+ custom_headers={},
173
+ provider_specific=provider_config.get("provider_specific", {}),
174
+ )
175
+
176
+
177
+ class AgentPersona(ABC):
178
+ """
179
+ Abstract base class for agent personas.
180
+
181
+ Defines the interface and common functionality for all agent personas.
182
+ """
183
+
184
+ def __init__(
185
+ self,
186
+ persona_id: str,
187
+ name: str,
188
+ description: str,
189
+ category: PersonaCategory = PersonaCategory.GENERAL,
190
+ template_manager: Optional[ConfigTemplateManager] = None,
191
+ ):
192
+ """
193
+ Initialize agent persona.
194
+
195
+ Args:
196
+ persona_id: Unique identifier for the persona
197
+ name: Human-readable name
198
+ description: Description of the persona's purpose
199
+ category: Category for organization
200
+ template_manager: Configuration template manager
201
+ """
202
+ self.id = persona_id
203
+ self.name = name
204
+ self.description = description
205
+ self.category = category
206
+ self.status = PersonaStatus.AVAILABLE
207
+ self.icon = self._get_default_icon()
208
+ self.template_manager = template_manager or ConfigTemplateManager()
209
+
210
+ # Core properties
211
+ self.capabilities: Set[PersonaCapability] = set()
212
+ self.configuration: Optional[PersonaConfiguration] = None
213
+ self.metadata = PersonaMetadata()
214
+
215
+ # State management
216
+ self._is_active = False
217
+ self._session_data: Dict[str, Any] = {}
218
+ self._event_listeners: List[Callable] = []
219
+
220
+ @abstractmethod
221
+ def _get_default_icon(self) -> str:
222
+ """Get default icon for this persona type."""
223
+ pass
224
+
225
+ @abstractmethod
226
+ def _setup_capabilities(self) -> Set[PersonaCapability]:
227
+ """Set up persona-specific capabilities."""
228
+ pass
229
+
230
+ @abstractmethod
231
+ def _setup_configuration(self) -> PersonaConfiguration:
232
+ """Set up persona-specific configuration."""
233
+ pass
234
+
235
+ def get_template(self) -> Optional[ConfigTemplate]:
236
+ """Get the underlying configuration template."""
237
+ if not self.configuration:
238
+ return None
239
+ return self.configuration.get_template(self.template_manager)
240
+
241
+ def get_primary_provider_config(self) -> Optional[Dict[str, Any]]:
242
+ """Get provider configuration for the primary provider."""
243
+ if not self.configuration:
244
+ return None
245
+ return self.configuration.get_primary_provider_config(self.template_manager)
246
+
247
+ def initialize(self) -> None:
248
+ """Initialize the persona with capabilities and configuration."""
249
+ self.capabilities = self._setup_capabilities()
250
+ self.configuration = self._setup_configuration()
251
+ self.status = PersonaStatus.AVAILABLE
252
+
253
+ def activate(self) -> bool:
254
+ """
255
+ Activate the persona.
256
+
257
+ Returns:
258
+ True if activation was successful
259
+ """
260
+ try:
261
+ self.status = PersonaStatus.LOADING
262
+ self._is_active = True
263
+ self.metadata.update_usage()
264
+ self.status = PersonaStatus.ACTIVE
265
+
266
+ # Notify listeners
267
+ for listener in self._event_listeners:
268
+ try:
269
+ listener("persona_activated", {"persona_id": self.id})
270
+ except Exception as e:
271
+ logger.error(f"Error in persona event listener: {e}")
272
+
273
+ return True
274
+ except Exception as e:
275
+ logger.error(f"Failed to activate persona {self.id}: {e}")
276
+ self.status = PersonaStatus.ERROR
277
+ return False
278
+
279
+ def deactivate(self) -> bool:
280
+ """
281
+ Deactivate the persona.
282
+
283
+ Returns:
284
+ True if deactivation was successful
285
+ """
286
+ try:
287
+ self._is_active = False
288
+ self.status = PersonaStatus.AVAILABLE
289
+ self._session_data.clear()
290
+
291
+ # Notify listeners
292
+ for listener in self._event_listeners:
293
+ try:
294
+ listener("persona_deactivated", {"persona_id": self.id})
295
+ except Exception as e:
296
+ logger.error(f"Error in persona event listener: {e}")
297
+
298
+ return True
299
+ except Exception as e:
300
+ logger.error(f"Failed to deactivate persona {self.id}: {e}")
301
+ return False
302
+
303
+ @property
304
+ def is_active(self) -> bool:
305
+ """Check if persona is currently active."""
306
+ return self._is_active and self.status == PersonaStatus.ACTIVE
307
+
308
+ def get_session_data(self, key: str, default: Any = None) -> Any:
309
+ """Get session-specific data."""
310
+ return self._session_data.get(key, default)
311
+
312
+ def set_session_data(self, key: str, value: Any) -> None:
313
+ """Set session-specific data."""
314
+ self._session_data[key] = value
315
+
316
+ def add_event_listener(self, listener: Callable) -> None:
317
+ """Add event listener for persona events."""
318
+ self._event_listeners.append(listener)
319
+
320
+ def remove_event_listener(self, listener: Callable) -> None:
321
+ """Remove event listener."""
322
+ if listener in self._event_listeners:
323
+ self._event_listeners.remove(listener)
324
+
325
+ def to_dict(self) -> Dict[str, Any]:
326
+ """Convert persona to dictionary representation."""
327
+ return {
328
+ "id": self.id,
329
+ "name": self.name,
330
+ "description": self.description,
331
+ "category": self.category.value,
332
+ "status": self.status.value,
333
+ "icon": self.icon,
334
+ "capabilities": [cap.value for cap in self.capabilities],
335
+ "configuration": asdict(self.configuration),
336
+ "metadata": asdict(self.metadata),
337
+ "is_active": self.is_active,
338
+ }
339
+
340
+ @classmethod
341
+ def from_dict(cls, data: Dict[str, Any]) -> "AgentPersona":
342
+ """Create persona from dictionary representation."""
343
+ # This would be implemented by specific persona types
344
+ raise NotImplementedError("Subclasses must implement from_dict")
345
+
346
+
347
+ class CodingPersona(AgentPersona):
348
+ """Agent persona optimized for software development."""
349
+
350
+ def __init__(self, template_manager: Optional[ConfigTemplateManager] = None):
351
+ super().__init__(
352
+ persona_id="coding",
353
+ name="Coding Agent",
354
+ description="Optimized for software development with code-capable models and dev tools",
355
+ category=PersonaCategory.DEVELOPMENT,
356
+ template_manager=template_manager,
357
+ )
358
+ self.initialize()
359
+
360
+ def _get_default_icon(self) -> str:
361
+ return "💻"
362
+
363
+ def _setup_capabilities(self) -> Set[PersonaCapability]:
364
+ return {
365
+ PersonaCapability.TOOL_CALLING,
366
+ PersonaCapability.CODE_GENERATION,
367
+ PersonaCapability.FILE_OPERATIONS,
368
+ PersonaCapability.FUNCTION_CALLING,
369
+ PersonaCapability.REASONING,
370
+ PersonaCapability.ANALYSIS,
371
+ }
372
+
373
+ def _setup_configuration(self) -> PersonaConfiguration:
374
+ return PersonaConfiguration(
375
+ template_id="coding",
376
+ primary_provider="claude",
377
+ fallback_providers=[
378
+ "openai",
379
+ "perplexity",
380
+ "claude-code",
381
+ "ollama",
382
+ ],
383
+ tools_enabled=True,
384
+ file_operations_enabled=True,
385
+ approval_required=True,
386
+ auto_approve_safe_operations=True,
387
+ )
388
+
389
+
390
+ class ResearchPersona(AgentPersona):
391
+ """Agent persona configured for research with web search capabilities."""
392
+
393
+ def __init__(self, template_manager: Optional[ConfigTemplateManager] = None):
394
+ super().__init__(
395
+ persona_id="research",
396
+ name="Research Agent",
397
+ description="Configured for research with web search and large context models",
398
+ category=PersonaCategory.RESEARCH,
399
+ template_manager=template_manager,
400
+ )
401
+ self.initialize()
402
+
403
+ def _get_default_icon(self) -> str:
404
+ return "🔍"
405
+
406
+ def _setup_capabilities(self) -> Set[PersonaCapability]:
407
+ return {
408
+ PersonaCapability.WEB_SEARCH,
409
+ PersonaCapability.LARGE_CONTEXT,
410
+ PersonaCapability.RESEARCH,
411
+ PersonaCapability.ANALYSIS,
412
+ PersonaCapability.REASONING,
413
+ }
414
+
415
+ def _setup_configuration(self) -> PersonaConfiguration:
416
+ return PersonaConfiguration(
417
+ template_id="research",
418
+ primary_provider="perplexity",
419
+ fallback_providers=["claude", "gemini", "openai", "xai"],
420
+ web_search_enabled=True,
421
+ tools_enabled=True,
422
+ file_operations_enabled=True,
423
+ approval_required=True,
424
+ )
425
+
426
+
427
+ class CreativePersona(AgentPersona):
428
+ """Agent persona set up for creative writing."""
429
+
430
+ def __init__(self, template_manager: Optional[ConfigTemplateManager] = None):
431
+ super().__init__(
432
+ persona_id="creative",
433
+ name="Creative Agent",
434
+ description="Set up for creative writing with high-temperature creative models",
435
+ category=PersonaCategory.CREATIVE,
436
+ template_manager=template_manager,
437
+ )
438
+ self.initialize()
439
+
440
+ def _get_default_icon(self) -> str:
441
+ return "🎨"
442
+
443
+ def _setup_capabilities(self) -> Set[PersonaCapability]:
444
+ return {
445
+ PersonaCapability.CREATIVE_WRITING,
446
+ PersonaCapability.HIGH_TEMPERATURE,
447
+ PersonaCapability.LARGE_CONTEXT,
448
+ }
449
+
450
+ def _setup_configuration(self) -> PersonaConfiguration:
451
+ return PersonaConfiguration(
452
+ template_id="creative",
453
+ primary_provider="claude",
454
+ fallback_providers=["openai", "mistral", "cohere", "gemini"],
455
+ tools_enabled=False,
456
+ web_search_enabled=False,
457
+ file_operations_enabled=True,
458
+ approval_required=False,
459
+ )
460
+
461
+
462
+ class PerformancePersona(AgentPersona):
463
+ """Agent persona optimized for speed and cost efficiency."""
464
+
465
+ def __init__(self, template_manager: Optional[ConfigTemplateManager] = None):
466
+ super().__init__(
467
+ persona_id="performance",
468
+ name="Performance Agent",
469
+ description="Optimized for speed and cost efficiency",
470
+ category=PersonaCategory.PRODUCTIVITY,
471
+ template_manager=template_manager,
472
+ )
473
+ self.initialize()
474
+
475
+ def _get_default_icon(self) -> str:
476
+ return "⚡"
477
+
478
+ def _setup_capabilities(self) -> Set[PersonaCapability]:
479
+ return {
480
+ PersonaCapability.FAST_RESPONSE,
481
+ PersonaCapability.COST_EFFICIENT,
482
+ PersonaCapability.TOOL_CALLING,
483
+ }
484
+
485
+ def _setup_configuration(self) -> PersonaConfiguration:
486
+ return PersonaConfiguration(
487
+ template_id="performance",
488
+ primary_provider="openai",
489
+ fallback_providers=["claude", "gemini", "ollama", "claude-code"],
490
+ timeout_override=15,
491
+ tools_enabled=True,
492
+ web_search_enabled=False,
493
+ file_operations_enabled=True,
494
+ approval_required=True,
495
+ auto_approve_safe_operations=True,
496
+ )
497
+
498
+
499
+ class GeneralPersona(AgentPersona):
500
+ """Agent persona with balanced configuration for general-purpose use."""
501
+
502
+ def __init__(self, template_manager: Optional[ConfigTemplateManager] = None):
503
+ super().__init__(
504
+ persona_id="general",
505
+ name="General Agent",
506
+ description="Balanced configuration for general-purpose use",
507
+ category=PersonaCategory.GENERAL,
508
+ template_manager=template_manager,
509
+ )
510
+ self.initialize()
511
+
512
+ def _get_default_icon(self) -> str:
513
+ return "🤖"
514
+
515
+ def _setup_capabilities(self) -> Set[PersonaCapability]:
516
+ return {
517
+ PersonaCapability.BALANCED,
518
+ PersonaCapability.GENERAL_PURPOSE,
519
+ PersonaCapability.TOOL_CALLING,
520
+ PersonaCapability.REASONING,
521
+ }
522
+
523
+ def _setup_configuration(self) -> PersonaConfiguration:
524
+ return PersonaConfiguration(
525
+ template_id="general",
526
+ primary_provider="claude",
527
+ fallback_providers=["openai", "gemini", "perplexity", "ollama"],
528
+ tools_enabled=True,
529
+ web_search_enabled=False,
530
+ file_operations_enabled=True,
531
+ approval_required=True,
532
+ )
533
+
534
+
535
+ class PersonaManager:
536
+ """
537
+ Manages agent personas and their lifecycle.
538
+
539
+ Handles persona registration, activation, deactivation, and integration
540
+ with the existing configuration system.
541
+ """
542
+
543
+ def __init__(
544
+ self,
545
+ agent_config: Optional[AgentConfig] = None,
546
+ status_manager: Optional[AgentStatusManager] = None,
547
+ template_manager: Optional[ConfigTemplateManager] = None,
548
+ ):
549
+ """
550
+ Initialize persona manager.
551
+
552
+ Args:
553
+ agent_config: Agent configuration instance
554
+ status_manager: Agent status manager for tracking
555
+ template_manager: Configuration template manager
556
+ """
557
+ self.agent_config = agent_config
558
+ self.status_manager = status_manager or get_status_manager()
559
+ self.template_manager = template_manager or ConfigTemplateManager()
560
+
561
+ # Persona storage
562
+ self.personas: Dict[str, AgentPersona] = {}
563
+ self.active_persona: Optional[AgentPersona] = None
564
+
565
+ # Built-in personas
566
+ self._builtin_personas = {
567
+ "coding": CodingPersona,
568
+ "research": ResearchPersona,
569
+ "creative": CreativePersona,
570
+ "performance": PerformancePersona,
571
+ "general": GeneralPersona,
572
+ }
573
+
574
+ # Custom personas (loaded from configuration)
575
+ self.custom_personas: Dict[str, Dict[str, Any]] = {}
576
+
577
+ # Event listeners
578
+ self._event_listeners: List[Callable] = []
579
+
580
+ # Initialize built-in personas
581
+ self._initialize_builtin_personas()
582
+
583
+ def _initialize_builtin_personas(self) -> None:
584
+ """Initialize all built-in personas."""
585
+ for persona_id, persona_class in self._builtin_personas.items():
586
+ try:
587
+ persona = persona_class(template_manager=self.template_manager)
588
+ self.personas[persona_id] = persona
589
+ persona.add_event_listener(self._handle_persona_event)
590
+ logger.debug(f"Initialized built-in persona: {persona_id}")
591
+ except Exception as e:
592
+ logger.error(f"Failed to initialize persona {persona_id}: {e}")
593
+
594
+ def _handle_persona_event(self, event_type: str, data: Dict[str, Any]) -> None:
595
+ """Handle persona events."""
596
+ for listener in self._event_listeners:
597
+ try:
598
+ listener(event_type, data)
599
+ except Exception as e:
600
+ logger.error(f"Error in persona manager event listener: {e}")
601
+
602
+ def get_persona(self, persona_id: str) -> Optional[AgentPersona]:
603
+ """Get persona by ID."""
604
+ return self.personas.get(persona_id)
605
+
606
+ def get_all_personas(self) -> Dict[str, AgentPersona]:
607
+ """Get all registered personas."""
608
+ return self.personas.copy()
609
+
610
+ def get_personas_by_category(self, category: PersonaCategory) -> List[AgentPersona]:
611
+ """Get personas filtered by category."""
612
+ return [p for p in self.personas.values() if p.category == category]
613
+
614
+ def get_available_personas(self) -> List[AgentPersona]:
615
+ """Get personas that are available for activation."""
616
+ return [
617
+ p
618
+ for p in self.personas.values()
619
+ if p.status in [PersonaStatus.AVAILABLE, PersonaStatus.ACTIVE]
620
+ ]
621
+
622
+ def activate_persona(self, persona_id: str) -> bool:
623
+ """
624
+ Activate a persona.
625
+
626
+ Args:
627
+ persona_id: ID of persona to activate
628
+
629
+ Returns:
630
+ True if activation was successful
631
+ """
632
+ persona = self.personas.get(persona_id)
633
+ if not persona:
634
+ logger.error(f"Persona not found: {persona_id}")
635
+ return False
636
+
637
+ try:
638
+ # Deactivate current persona if any
639
+ if self.active_persona and self.active_persona != persona:
640
+ self.deactivate_persona()
641
+
642
+ # Activate new persona
643
+ if persona.activate():
644
+ self.active_persona = persona
645
+
646
+ # Update agent configuration if available
647
+ if self.agent_config:
648
+ self._apply_persona_configuration(persona)
649
+
650
+ # Update status manager
651
+ if self.status_manager:
652
+ try:
653
+ import asyncio
654
+
655
+ # Try to get running loop, if not available, just skip status update
656
+ loop = asyncio.get_running_loop()
657
+ loop.create_task(
658
+ self.status_manager.set_agent_status(
659
+ agent_id=persona_id,
660
+ status=AgentStatus.ENABLED,
661
+ metadata={"persona_name": persona.name},
662
+ )
663
+ )
664
+ except RuntimeError:
665
+ # No event loop running, skip async status update
666
+ logger.debug(
667
+ f"No event loop available for status update of persona {persona_id}"
668
+ )
669
+ pass
670
+
671
+ logger.info(f"Activated persona: {persona_id} ({persona.name})")
672
+ return True
673
+
674
+ return False
675
+ except Exception as e:
676
+ logger.error(f"Failed to activate persona {persona_id}: {e}")
677
+ return False
678
+
679
+ def deactivate_persona(self) -> bool:
680
+ """
681
+ Deactivate the currently active persona.
682
+
683
+ Returns:
684
+ True if deactivation was successful
685
+ """
686
+ if not self.active_persona:
687
+ return True
688
+
689
+ try:
690
+ persona = self.active_persona
691
+ if persona.deactivate():
692
+ self.active_persona = None
693
+
694
+ # Update status manager
695
+ if self.status_manager:
696
+ try:
697
+ import asyncio
698
+
699
+ # Try to get running loop, if not available, just skip status update
700
+ loop = asyncio.get_running_loop()
701
+ loop.create_task(
702
+ self.status_manager.set_agent_status(
703
+ agent_id=persona.id,
704
+ status=AgentStatus.DISABLED,
705
+ )
706
+ )
707
+ except RuntimeError:
708
+ # No event loop running, skip async status update
709
+ logger.debug(
710
+ f"No event loop available for status update of persona {persona.id}"
711
+ )
712
+ pass
713
+
714
+ logger.info(f"Deactivated persona: {persona.id}")
715
+ return True
716
+
717
+ return False
718
+ except Exception as e:
719
+ logger.error(f"Failed to deactivate persona: {e}")
720
+ return False
721
+
722
+ def _apply_persona_configuration(self, persona: AgentPersona) -> None:
723
+ """Apply persona configuration to agent config."""
724
+ if not self.agent_config or not persona.configuration:
725
+ return
726
+
727
+ try:
728
+ # Convert persona configuration to provider config
729
+ provider_config = persona.configuration.to_provider_config(
730
+ persona.id, self.template_manager
731
+ )
732
+
733
+ # Add to agent configuration
734
+ self.agent_config.add_provider_config(
735
+ f"persona_{persona.id}", provider_config
736
+ )
737
+
738
+ logger.debug(f"Applied configuration for persona: {persona.id}")
739
+ except Exception as e:
740
+ logger.error(f"Failed to apply persona configuration: {e}")
741
+
742
+ def register_custom_persona(self, persona_data: Dict[str, Any]) -> bool:
743
+ """
744
+ Register a custom persona from configuration data.
745
+
746
+ Args:
747
+ persona_data: Persona configuration dictionary
748
+
749
+ Returns:
750
+ True if registration was successful
751
+ """
752
+ try:
753
+ # Validate required fields
754
+ required_fields = ["id", "name", "description", "configuration"]
755
+ for field in required_fields:
756
+ if field not in persona_data:
757
+ raise ValueError(f"Missing required field: {field}")
758
+
759
+ # Create custom persona (this would need a CustomPersona class)
760
+ # For now, we'll store the data for later implementation
761
+ persona_id = persona_data["id"]
762
+ self.custom_personas[persona_id] = persona_data
763
+
764
+ logger.info(f"Registered custom persona: {persona_id}")
765
+ return True
766
+ except Exception as e:
767
+ logger.error(f"Failed to register custom persona: {e}")
768
+ return False
769
+
770
+ def get_persona_recommendations(self, context: str) -> List[AgentPersona]:
771
+ """
772
+ Get persona recommendations based on context.
773
+
774
+ Args:
775
+ context: Context description for recommendations
776
+
777
+ Returns:
778
+ List of recommended personas
779
+ """
780
+ # Simple keyword-based recommendations
781
+ # In a real implementation, this could use ML or more sophisticated matching
782
+ context_lower = context.lower()
783
+ recommendations = []
784
+
785
+ # Coding-related keywords
786
+ if any(
787
+ word in context_lower
788
+ for word in [
789
+ "code",
790
+ "programming",
791
+ "development",
792
+ "bug",
793
+ "function",
794
+ ]
795
+ ):
796
+ coding_persona = self.get_persona("coding")
797
+ if coding_persona:
798
+ recommendations.append(coding_persona)
799
+
800
+ # Research-related keywords
801
+ if any(
802
+ word in context_lower
803
+ for word in ["research", "search", "find", "information", "study"]
804
+ ):
805
+ research_persona = self.get_persona("research")
806
+ if research_persona:
807
+ recommendations.append(research_persona)
808
+
809
+ # Creative-related keywords
810
+ if any(
811
+ word in context_lower
812
+ for word in ["write", "story", "creative", "poem", "novel"]
813
+ ):
814
+ creative_persona = self.get_persona("creative")
815
+ if creative_persona:
816
+ recommendations.append(creative_persona)
817
+
818
+ # Performance-related keywords
819
+ if any(
820
+ word in context_lower for word in ["fast", "quick", "speed", "efficient"]
821
+ ):
822
+ performance_persona = self.get_persona("performance")
823
+ if performance_persona:
824
+ recommendations.append(performance_persona)
825
+
826
+ # Default to general if no specific matches
827
+ if not recommendations:
828
+ general_persona = self.get_persona("general")
829
+ if general_persona:
830
+ recommendations.append(general_persona)
831
+
832
+ return recommendations
833
+
834
+ def get_stats(self) -> Dict[str, Any]:
835
+ """Get persona manager statistics."""
836
+ return {
837
+ "total_personas": len(self.personas),
838
+ "builtin_personas": len(self._builtin_personas),
839
+ "custom_personas": len(self.custom_personas),
840
+ "active_persona": (self.active_persona.id if self.active_persona else None),
841
+ "available_personas": len(self.get_available_personas()),
842
+ "categories": list(set(p.category.value for p in self.personas.values())),
843
+ }
844
+
845
+ def add_event_listener(self, listener: Callable) -> None:
846
+ """Add event listener for persona manager events."""
847
+ self._event_listeners.append(listener)
848
+
849
+ def remove_event_listener(self, listener: Callable) -> None:
850
+ """Remove event listener."""
851
+ if listener in self._event_listeners:
852
+ self._event_listeners.remove(listener)
853
+
854
+
855
+ # Global instance for easy access
856
+ _global_persona_manager: Optional[PersonaManager] = None
857
+
858
+
859
+ def get_persona_manager() -> PersonaManager:
860
+ """Get the global persona manager instance."""
861
+ global _global_persona_manager
862
+ if _global_persona_manager is None:
863
+ _global_persona_manager = PersonaManager()
864
+ return _global_persona_manager
865
+
866
+
867
+ def set_persona_manager(manager: PersonaManager) -> None:
868
+ """Set the global persona manager instance."""
869
+ global _global_persona_manager
870
+ _global_persona_manager = manager