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,545 @@
1
+ """
2
+ Agent Progress UI Components for Omnimancer.
3
+
4
+ This module provides Rich-based UI components for displaying agent mode
5
+ progress, operation status, and real-time updates.
6
+ """
7
+
8
+ import asyncio
9
+ from contextlib import asynccontextmanager
10
+ from dataclasses import dataclass
11
+ from datetime import datetime, timedelta
12
+ from typing import Dict, Optional
13
+
14
+ from rich.align import Align
15
+ from rich.console import Console
16
+ from rich.layout import Layout
17
+ from rich.live import Live
18
+ from rich.panel import Panel
19
+ from rich.progress import (
20
+ BarColumn,
21
+ Progress,
22
+ SpinnerColumn,
23
+ TaskID,
24
+ TextColumn,
25
+ TimeRemainingColumn,
26
+ )
27
+ from rich.table import Table
28
+ from rich.text import Text
29
+
30
+ from .agent_mode_manager import (
31
+ AgentMode,
32
+ AgentModeManager,
33
+ AgentOperation,
34
+ AgentOperationStatus,
35
+ )
36
+
37
+
38
+ @dataclass
39
+ class OperationProgress:
40
+ """Tracks progress of an individual operation."""
41
+
42
+ operation_id: str
43
+ task_id: Optional[TaskID] = None
44
+ progress: float = 0.0
45
+ status_text: str = "Queued"
46
+ started_at: Optional[datetime] = None
47
+ estimated_duration: Optional[float] = None
48
+
49
+
50
+ class AgentProgressUI:
51
+ """
52
+ Rich-based UI for displaying agent mode progress and status.
53
+
54
+ Provides real-time updates of:
55
+ - Agent mode status
56
+ - Operation queue status
57
+ - Individual operation progress
58
+ - System metrics
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ agent_manager: AgentModeManager,
64
+ console: Optional[Console] = None,
65
+ ):
66
+ """
67
+ Initialize the progress UI.
68
+
69
+ Args:
70
+ agent_manager: Agent mode manager instance
71
+ console: Rich console instance (creates new if None)
72
+ """
73
+ self.agent_manager = agent_manager
74
+ self.console = console or Console()
75
+
76
+ # Progress tracking
77
+ self.progress = Progress(
78
+ SpinnerColumn(),
79
+ TextColumn("[progress.description]{task.description}"),
80
+ BarColumn(),
81
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
82
+ TimeRemainingColumn(),
83
+ console=self.console,
84
+ transient=True,
85
+ )
86
+
87
+ # Operation tracking
88
+ self.operation_progress: Dict[str, OperationProgress] = {}
89
+ self.operation_task_ids: Dict[str, TaskID] = {}
90
+
91
+ # UI state
92
+ self.live_display: Optional[Live] = None
93
+ self.update_interval = 0.5 # seconds
94
+ self._update_task: Optional[asyncio.Task] = None
95
+ self._stop_event = asyncio.Event()
96
+
97
+ # Register callbacks
98
+ self.agent_manager.add_operation_callback(self._on_operation_change)
99
+ self.agent_manager.add_mode_change_callback(self._on_mode_change)
100
+
101
+ def start_monitoring(self):
102
+ """Start real-time monitoring display."""
103
+ if self._update_task and not self._update_task.done():
104
+ return # Already running
105
+
106
+ self._stop_event.clear()
107
+ self._update_task = asyncio.create_task(self._monitoring_loop())
108
+
109
+ def stop_monitoring(self):
110
+ """Stop real-time monitoring display."""
111
+ self._stop_event.set()
112
+ if self._update_task and not self._update_task.done():
113
+ self._update_task.cancel()
114
+
115
+ @asynccontextmanager
116
+ async def live_status_display(self):
117
+ """Context manager for live status display."""
118
+ layout = self._create_status_layout()
119
+
120
+ with Live(layout, console=self.console, refresh_per_second=2) as live:
121
+ self.live_display = live
122
+ try:
123
+ yield live
124
+ finally:
125
+ self.live_display = None
126
+
127
+ def show_status_panel(self) -> Panel:
128
+ """
129
+ Create and return a status panel.
130
+
131
+ Returns:
132
+ Rich Panel with current status
133
+ """
134
+ status_info = self.agent_manager.get_status()
135
+
136
+ # Mode status
137
+ mode_color = {"off": "red", "on": "green", "paused": "yellow"}.get(
138
+ status_info["mode"], "white"
139
+ )
140
+
141
+ mode_text = Text(f"Agent Mode: ", style="bold")
142
+ mode_text.append(status_info["mode"].upper(), style=f"bold {mode_color}")
143
+
144
+ # Operation counts
145
+ ops = status_info["operations"]
146
+ ops_text = f"""
147
+ Queued: {ops['queued']}
148
+ In Progress: {ops['in_progress']}
149
+ Completed: {ops['completed']}
150
+ Failed: {ops['failed']}
151
+ Requires Approval: {ops['requires_approval']}"""
152
+
153
+ # Settings info
154
+ settings = status_info["settings"]
155
+ settings_text = f"""
156
+ Auto-approve Low Risk: {'Yes' if settings['auto_approve_low_risk'] else 'No'}
157
+ Auto-approve Read-only: {'Yes' if settings['auto_approve_read_only'] else 'No'}
158
+ Max Concurrent: {settings['max_concurrent_operations']}
159
+ Batch Approval: {'Enabled' if settings['enable_batch_approval'] else 'Disabled'}"""
160
+
161
+ status_content = (
162
+ f"{mode_text}\n\nOperations:{ops_text}\n\nSettings:{settings_text}"
163
+ )
164
+
165
+ return Panel(
166
+ status_content,
167
+ title="Agent Status",
168
+ border_style="blue",
169
+ padding=(1, 2),
170
+ )
171
+
172
+ def show_operations_table(self, limit: int = 10) -> Table:
173
+ """
174
+ Create and return an operations table.
175
+
176
+ Args:
177
+ limit: Maximum number of operations to show
178
+
179
+ Returns:
180
+ Rich Table with operation history
181
+ """
182
+ table = Table(
183
+ title=f"Recent Operations (Last {limit})",
184
+ show_header=True,
185
+ header_style="bold magenta",
186
+ border_style="blue",
187
+ )
188
+
189
+ table.add_column("ID", style="dim", width=12)
190
+ table.add_column("Type", style="cyan", width=15)
191
+ table.add_column("Description", width=30)
192
+ table.add_column("Status", width=12)
193
+ table.add_column("Duration", width=10)
194
+ table.add_column("Retries", width=8)
195
+
196
+ history = self.agent_manager.get_operation_history(limit)
197
+
198
+ for op in history:
199
+ # Format status with color
200
+ status = op["status"]
201
+ status_color = {
202
+ "queued": "yellow",
203
+ "in_progress": "blue",
204
+ "completed": "green",
205
+ "failed": "red",
206
+ "cancelled": "red",
207
+ "requires_approval": "magenta",
208
+ }.get(status, "white")
209
+
210
+ status_text = Text(status.replace("_", " ").title(), style=status_color)
211
+
212
+ # Format duration
213
+ duration = op.get("duration")
214
+ if duration:
215
+ if duration < 60:
216
+ duration_str = f"{duration:.1f}s"
217
+ else:
218
+ duration_str = f"{duration/60:.1f}m"
219
+ else:
220
+ duration_str = "-"
221
+
222
+ # Format retry count
223
+ retry_count = op.get("retry_count", 0)
224
+ retry_str = str(retry_count) if retry_count > 0 else "-"
225
+
226
+ table.add_row(
227
+ op["id"][-8:], # Show last 8 chars of ID
228
+ op["type"],
229
+ (
230
+ op["description"][:28] + "..."
231
+ if len(op["description"]) > 28
232
+ else op["description"]
233
+ ),
234
+ status_text,
235
+ duration_str,
236
+ retry_str,
237
+ )
238
+
239
+ if not history:
240
+ table.add_row("", "", "No operations yet", "", "", "")
241
+
242
+ return table
243
+
244
+ def show_progress_panel(self) -> Panel:
245
+ """
246
+ Create and return a progress panel for active operations.
247
+
248
+ Returns:
249
+ Rich Panel with progress information
250
+ """
251
+ active_ops = list(self.agent_manager.active_operations.values())
252
+
253
+ if not active_ops:
254
+ content = Text("No operations currently running", style="dim italic")
255
+ return Panel(
256
+ Align.center(content),
257
+ title="Active Operations",
258
+ border_style="blue",
259
+ height=5,
260
+ )
261
+
262
+ # Create progress display
263
+ progress_text = []
264
+ for op in active_ops:
265
+ # Calculate elapsed time
266
+ elapsed = datetime.now() - op.started_at if op.started_at else timedelta(0)
267
+ elapsed_str = f"{elapsed.total_seconds():.1f}s"
268
+
269
+ # Get progress if available
270
+ op_progress = self.operation_progress.get(op.id)
271
+ if op_progress and op_progress.progress > 0:
272
+ progress_str = f"[{int(op_progress.progress)}%]"
273
+ status = op_progress.status_text
274
+ else:
275
+ progress_str = "[---]"
276
+ status = op.status.value.replace("_", " ").title()
277
+
278
+ line = f"• {op.operation.type.value}: {op.operation.description[:30]}"
279
+ if len(op.operation.description) > 30:
280
+ line += "..."
281
+ line += f" {progress_str} ({elapsed_str}) - {status}"
282
+
283
+ progress_text.append(line)
284
+
285
+ content = "\n".join(progress_text)
286
+
287
+ return Panel(
288
+ content,
289
+ title="Active Operations",
290
+ border_style="green",
291
+ padding=(1, 2),
292
+ )
293
+
294
+ def show_approval_queue_panel(self) -> Panel:
295
+ """
296
+ Create and return a panel showing operations requiring approval.
297
+
298
+ Returns:
299
+ Rich Panel with approval queue
300
+ """
301
+ approval_ops = [
302
+ op
303
+ for op in self.agent_manager.operation_queue
304
+ if op.status == AgentOperationStatus.REQUIRES_APPROVAL
305
+ ]
306
+
307
+ if not approval_ops:
308
+ content = Text("No operations waiting for approval", style="dim italic")
309
+ return Panel(
310
+ Align.center(content),
311
+ title="Approval Queue",
312
+ border_style="yellow",
313
+ height=5,
314
+ )
315
+
316
+ approval_text = []
317
+ for i, op in enumerate(approval_ops[:5], 1): # Show up to 5
318
+ risk = op.operation.data.get("risk_level", "medium")
319
+ risk_color = {
320
+ "low": "green",
321
+ "medium": "yellow",
322
+ "high": "red",
323
+ "critical": "magenta",
324
+ }.get(risk.lower(), "white")
325
+
326
+ line = f"{i}. {op.operation.description[:40]}"
327
+ if len(op.operation.description) > 40:
328
+ line += "..."
329
+ line += f" [Risk: {risk}]"
330
+
331
+ # Create colored text
332
+ text = Text(line)
333
+ text.stylize(risk_color, len(line) - len(f"[Risk: {risk}]"), len(line))
334
+ approval_text.append(text)
335
+
336
+ if len(approval_ops) > 5:
337
+ approval_text.append(
338
+ Text(f"... and {len(approval_ops) - 5} more", style="dim")
339
+ )
340
+
341
+ content = "\n".join(str(text) for text in approval_text)
342
+
343
+ return Panel(
344
+ content,
345
+ title=f"Approval Queue ({len(approval_ops)})",
346
+ border_style="yellow",
347
+ padding=(1, 2),
348
+ )
349
+
350
+ def show_agent_dashboard(self) -> Layout:
351
+ """
352
+ Create a comprehensive agent dashboard layout.
353
+
354
+ Returns:
355
+ Rich Layout with full dashboard
356
+ """
357
+ layout = Layout()
358
+
359
+ # Split into sections
360
+ layout.split(
361
+ Layout(name="header", size=8),
362
+ Layout(name="body"),
363
+ Layout(name="footer", size=4),
364
+ )
365
+
366
+ # Header: Status and settings
367
+ layout["header"].split_row(
368
+ Layout(self.show_status_panel(), name="status"),
369
+ Layout(self.show_approval_queue_panel(), name="approvals"),
370
+ )
371
+
372
+ # Body: Operations and progress
373
+ layout["body"].split_row(
374
+ Layout(self.show_operations_table(8), name="operations"),
375
+ Layout(self.show_progress_panel(), name="progress"),
376
+ )
377
+
378
+ # Footer: Help text
379
+ help_text = Text(
380
+ "Commands: /agent on|off|status • Ctrl+C to exit dashboard • Use /agent off to disable agent mode",
381
+ style="dim",
382
+ )
383
+ layout["footer"] = Panel(Align.center(help_text), style="dim")
384
+
385
+ return layout
386
+
387
+ def update_operation_progress(
388
+ self, operation_id: str, progress: float, status_text: str = ""
389
+ ):
390
+ """
391
+ Update progress for a specific operation.
392
+
393
+ Args:
394
+ operation_id: ID of the operation
395
+ progress: Progress percentage (0-100)
396
+ status_text: Current status description
397
+ """
398
+ if operation_id not in self.operation_progress:
399
+ self.operation_progress[operation_id] = OperationProgress(
400
+ operation_id=operation_id, started_at=datetime.now()
401
+ )
402
+
403
+ op_progress = self.operation_progress[operation_id]
404
+ op_progress.progress = progress
405
+ if status_text:
406
+ op_progress.status_text = status_text
407
+
408
+ # Update Rich progress bar if exists
409
+ if operation_id in self.operation_task_ids:
410
+ task_id = self.operation_task_ids[operation_id]
411
+ self.progress.update(task_id, completed=progress, description=status_text)
412
+
413
+ def _create_status_layout(self) -> Layout:
414
+ """Create the main status layout for live display."""
415
+ return self.show_agent_dashboard()
416
+
417
+ async def _monitoring_loop(self):
418
+ """Main monitoring loop for real-time updates."""
419
+ try:
420
+ while not self._stop_event.is_set():
421
+ # Update live display if active
422
+ if self.live_display:
423
+ self.live_display.update(self._create_status_layout())
424
+
425
+ # Wait for next update
426
+ try:
427
+ await asyncio.wait_for(
428
+ self._stop_event.wait(), timeout=self.update_interval
429
+ )
430
+ break # Stop event was set
431
+ except asyncio.TimeoutError:
432
+ continue # Normal timeout, continue monitoring
433
+
434
+ except asyncio.CancelledError:
435
+ pass
436
+ except Exception as e:
437
+ self.console.print(f"[red]Error in monitoring loop: {e}[/red]")
438
+
439
+ def _on_operation_change(self, operation: AgentOperation):
440
+ """Handle operation state changes."""
441
+ # Update progress tracking
442
+ if operation.status == AgentOperationStatus.IN_PROGRESS:
443
+ # Start tracking progress
444
+ if operation.id not in self.operation_progress:
445
+ self.operation_progress[operation.id] = OperationProgress(
446
+ operation_id=operation.id,
447
+ started_at=operation.started_at,
448
+ status_text="Starting...",
449
+ )
450
+
451
+ elif operation.status in [
452
+ AgentOperationStatus.COMPLETED,
453
+ AgentOperationStatus.FAILED,
454
+ AgentOperationStatus.CANCELLED,
455
+ ]:
456
+ # Update final progress
457
+ if operation.id in self.operation_progress:
458
+ op_progress = self.operation_progress[operation.id]
459
+ op_progress.progress = (
460
+ 100.0 if operation.status == AgentOperationStatus.COMPLETED else 0.0
461
+ )
462
+ op_progress.status_text = operation.status.value.replace(
463
+ "_", " "
464
+ ).title()
465
+
466
+ # Clean up progress tracking after delay
467
+ asyncio.create_task(self._cleanup_operation_progress(operation.id))
468
+
469
+ def _on_mode_change(self, old_mode: AgentMode, new_mode: AgentMode):
470
+ """Handle agent mode changes."""
471
+ if new_mode == AgentMode.ON:
472
+ # Start monitoring if not already running
473
+ if not self._update_task or self._update_task.done():
474
+ self.start_monitoring()
475
+ elif new_mode == AgentMode.OFF:
476
+ # Stop monitoring
477
+ self.stop_monitoring()
478
+
479
+ async def _cleanup_operation_progress(self, operation_id: str, delay: float = 5.0):
480
+ """Clean up operation progress tracking after delay."""
481
+ await asyncio.sleep(delay)
482
+
483
+ # Remove from tracking
484
+ self.operation_progress.pop(operation_id, None)
485
+
486
+ # Remove from Rich progress
487
+ if operation_id in self.operation_task_ids:
488
+ task_id = self.operation_task_ids.pop(operation_id)
489
+ try:
490
+ self.progress.remove_task(task_id)
491
+ except KeyError:
492
+ pass # Task already removed
493
+
494
+
495
+ class AgentStatusIndicator:
496
+ """Simple status indicator for embedding in other UI components."""
497
+
498
+ def __init__(self, agent_manager: AgentModeManager):
499
+ """
500
+ Initialize status indicator.
501
+
502
+ Args:
503
+ agent_manager: Agent mode manager instance
504
+ """
505
+ self.agent_manager = agent_manager
506
+
507
+ def get_status_badge(self) -> Text:
508
+ """
509
+ Get a small status badge for display.
510
+
511
+ Returns:
512
+ Rich Text object with status badge
513
+ """
514
+ status = self.agent_manager.get_status()
515
+ mode = status["mode"]
516
+
517
+ if mode == "on":
518
+ active_count = status["operations"]["in_progress"]
519
+ if active_count > 0:
520
+ return Text(f"🤖 AGENT[{active_count}]", style="bold green")
521
+ else:
522
+ return Text("🤖 AGENT", style="bold green")
523
+ elif mode == "paused":
524
+ return Text("🤖 PAUSED", style="bold yellow")
525
+ else:
526
+ return Text("🤖 OFF", style="dim red")
527
+
528
+ def get_mini_status(self) -> str:
529
+ """
530
+ Get minimal status string.
531
+
532
+ Returns:
533
+ Short status string
534
+ """
535
+ status = self.agent_manager.get_status()
536
+ mode = status["mode"]
537
+
538
+ if mode == "on":
539
+ active = status["operations"]["in_progress"]
540
+ queued = status["operations"]["queued"]
541
+ return f"Agent: ON ({active} active, {queued} queued)"
542
+ elif mode == "paused":
543
+ return "Agent: PAUSED"
544
+ else:
545
+ return "Agent: OFF"
@@ -0,0 +1,141 @@
1
+ """
2
+ Chat manager for Omnimancer.
3
+
4
+ This module handles conversation management, context tracking,
5
+ and message history for chat sessions.
6
+ """
7
+
8
+ import uuid
9
+ from datetime import datetime
10
+ from typing import Optional
11
+
12
+ from .models import ChatContext, ChatMessage, MessageRole
13
+
14
+
15
+ class ChatManager:
16
+ """
17
+ Manages chat conversations and context.
18
+
19
+ This class handles the conversation state, message history,
20
+ and context management for chat sessions.
21
+ """
22
+
23
+ def __init__(self):
24
+ """Initialize the chat manager."""
25
+ self.current_context: Optional[ChatContext] = None
26
+ self._initialize_context()
27
+
28
+ def _initialize_context(self) -> None:
29
+ """Initialize a new chat context."""
30
+ self.current_context = ChatContext(
31
+ messages=[],
32
+ current_model="",
33
+ session_id=str(uuid.uuid4()),
34
+ )
35
+
36
+ def get_current_context(self) -> ChatContext:
37
+ """
38
+ Get the current chat context.
39
+
40
+ Returns:
41
+ Current ChatContext instance
42
+ """
43
+ if self.current_context is None:
44
+ self._initialize_context()
45
+ return self.current_context
46
+
47
+ def set_current_model(self, model: str) -> None:
48
+ """
49
+ Set the current model being used.
50
+
51
+ Args:
52
+ model: Model name
53
+ """
54
+ context = self.get_current_context()
55
+ context.current_model = model
56
+
57
+ def add_user_message(self, content: str) -> None:
58
+ """
59
+ Add a user message to the current context.
60
+
61
+ Args:
62
+ content: Message content
63
+ """
64
+ context = self.get_current_context()
65
+ message = ChatMessage(
66
+ role=MessageRole.USER,
67
+ content=content,
68
+ timestamp=datetime.now(),
69
+ model_used=context.current_model,
70
+ )
71
+ context.add_message(message)
72
+
73
+ def add_assistant_message(self, content: str, model_used: str) -> None:
74
+ """
75
+ Add an assistant message to the current context.
76
+
77
+ Args:
78
+ content: Message content
79
+ model_used: Model that generated the message
80
+ """
81
+ context = self.get_current_context()
82
+ message = ChatMessage(
83
+ role=MessageRole.ASSISTANT,
84
+ content=content,
85
+ timestamp=datetime.now(),
86
+ model_used=model_used,
87
+ )
88
+ context.add_message(message)
89
+
90
+ def add_system_message(self, content: str) -> None:
91
+ """
92
+ Add a system message to the current context.
93
+
94
+ Args:
95
+ content: Message content
96
+ """
97
+ context = self.get_current_context()
98
+ message = ChatMessage(
99
+ role=MessageRole.SYSTEM,
100
+ content=content,
101
+ timestamp=datetime.now(),
102
+ model_used=context.current_model,
103
+ )
104
+ context.add_message(message)
105
+
106
+ def clear_context(self) -> None:
107
+ """Clear the current conversation context."""
108
+ if self.current_context:
109
+ self.current_context.clear()
110
+
111
+ def start_new_session(self) -> str:
112
+ """
113
+ Start a new chat session.
114
+
115
+ Returns:
116
+ New session ID
117
+ """
118
+ self._initialize_context()
119
+ return self.current_context.session_id
120
+
121
+ def get_message_count(self) -> int:
122
+ """
123
+ Get the number of messages in the current context.
124
+
125
+ Returns:
126
+ Message count
127
+ """
128
+ context = self.get_current_context()
129
+ return len(context.messages)
130
+
131
+ def get_last_message(self) -> Optional[ChatMessage]:
132
+ """
133
+ Get the last message in the conversation.
134
+
135
+ Returns:
136
+ Last ChatMessage or None if no messages
137
+ """
138
+ context = self.get_current_context()
139
+ if context.messages:
140
+ return context.messages[-1]
141
+ return None