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,530 @@
1
+ """Main security manager that coordinates all security components."""
2
+
3
+ import uuid
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional, Union
6
+
7
+ from .approval_workflow import ApprovalStatus, ApprovalWorkflow, RiskLevel
8
+ from .audit_logger import AuditEventType, AuditLevel, AuditLogger
9
+ from .permission_controller import (
10
+ PermissionController,
11
+ PermissionOperation,
12
+ )
13
+ from .sandbox_manager import ResourceLimits, SandboxManager
14
+
15
+
16
+ class SecurityManager:
17
+ """Main security manager that coordinates all security components."""
18
+
19
+ def __init__(
20
+ self,
21
+ enable_sandbox: bool = True,
22
+ enable_approval_workflow: bool = True,
23
+ enable_audit_logging: bool = True,
24
+ default_resource_limits: Optional[ResourceLimits] = None,
25
+ audit_log_file: Optional[str] = None,
26
+ ):
27
+
28
+ # Initialize components
29
+ self.permissions = PermissionController()
30
+
31
+ self.sandbox = (
32
+ SandboxManager(default_resource_limits) if enable_sandbox else None
33
+ )
34
+ self.approval = ApprovalWorkflow() if enable_approval_workflow else None
35
+ self.audit = AuditLogger(audit_log_file) if enable_audit_logging else None
36
+
37
+ # Configuration
38
+ self.enable_sandbox = enable_sandbox
39
+ self.enable_approval_workflow = enable_approval_workflow
40
+ self.enable_audit_logging = enable_audit_logging
41
+
42
+ # Session tracking
43
+ self.session_id = str(uuid.uuid4())
44
+ self.operation_counter = 0
45
+
46
+ # Security policies
47
+ self.security_policies = self._get_default_security_policies()
48
+
49
+ if self.audit:
50
+ self.audit.log_event(
51
+ AuditEventType.SYSTEM_EVENT,
52
+ AuditLevel.INFO,
53
+ "SecurityManager initialized",
54
+ session_id=self.session_id,
55
+ metadata={
56
+ "sandbox_enabled": enable_sandbox,
57
+ "approval_workflow_enabled": enable_approval_workflow,
58
+ "audit_logging_enabled": enable_audit_logging,
59
+ },
60
+ )
61
+
62
+ def _get_default_security_policies(self) -> Dict[str, Any]:
63
+ """Get default security policies."""
64
+ return {
65
+ "require_approval_for_high_risk": True,
66
+ "sandbox_all_commands": True,
67
+ "log_all_operations": True,
68
+ "block_system_directories": True,
69
+ "block_credential_files": True,
70
+ "max_command_timeout": 300, # 5 minutes
71
+ "max_file_size_mb": 100,
72
+ "allowed_network_domains": [],
73
+ "blocked_network_domains": ["*.internal", "*.local"],
74
+ }
75
+
76
+ async def validate_operation(
77
+ self, operation: PermissionOperation
78
+ ) -> Dict[str, Any]:
79
+ """Validate if an operation is allowed and safe to execute."""
80
+
81
+ operation_id = f"op_{self.operation_counter}"
82
+ self.operation_counter += 1
83
+
84
+ result = {
85
+ "allowed": False,
86
+ "operation_id": operation_id,
87
+ "session_id": self.session_id,
88
+ "reasons": [],
89
+ "approval_required": False,
90
+ "approval_request_id": None,
91
+ "sandbox_required": False,
92
+ }
93
+
94
+ try:
95
+ # Step 1: Basic permission check
96
+ permission_allowed = self.permissions.validate_operation(operation)
97
+
98
+ if self.audit:
99
+ self.audit.log_permission_check(
100
+ operation.operation_type,
101
+ path=operation.path,
102
+ allowed=permission_allowed,
103
+ operation_id=operation_id,
104
+ session_id=self.session_id,
105
+ metadata=operation.metadata,
106
+ )
107
+
108
+ if not permission_allowed:
109
+ result["reasons"].append("Permission denied by security policy")
110
+ return result
111
+
112
+ # Step 2: Risk assessment and approval workflow
113
+ if self.approval and self.security_policies.get(
114
+ "require_approval_for_high_risk", True
115
+ ):
116
+ risk_level = self.approval.assess_risk_level(
117
+ operation.operation_type, operation.metadata
118
+ )
119
+
120
+ # Check if approval is required
121
+ if risk_level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
122
+ approval_request = await self.approval.request_approval(
123
+ operation.operation_type,
124
+ f"Operation: {operation.operation_type}",
125
+ operation.metadata,
126
+ )
127
+
128
+ if approval_request.status == ApprovalStatus.PENDING:
129
+ result["approval_required"] = True
130
+ result["approval_request_id"] = approval_request.id
131
+ result["reasons"].append(
132
+ f"Approval required for {risk_level.value} risk operation"
133
+ )
134
+ return result
135
+ elif approval_request.status != ApprovalStatus.APPROVED:
136
+ result["reasons"].append(
137
+ f"Operation not approved: {approval_request.status.value}"
138
+ )
139
+ return result
140
+
141
+ # Step 3: Determine if sandboxing is required
142
+ if self.sandbox and self.security_policies.get(
143
+ "sandbox_all_commands", True
144
+ ):
145
+ if operation.command or operation.operation_type in [
146
+ "command_execute",
147
+ "file_write",
148
+ "file_create",
149
+ ]:
150
+ result["sandbox_required"] = True
151
+
152
+ # Step 4: All checks passed
153
+ result["allowed"] = True
154
+ result["reasons"].append("All security checks passed")
155
+
156
+ return result
157
+
158
+ except Exception as e:
159
+ if self.audit:
160
+ self.audit.log_event(
161
+ AuditEventType.SECURITY_ALERT,
162
+ AuditLevel.ERROR,
163
+ f"Error validating operation: {str(e)}",
164
+ operation_id=operation_id,
165
+ session_id=self.session_id,
166
+ metadata={
167
+ "operation_type": operation.operation_type,
168
+ "error": str(e),
169
+ },
170
+ )
171
+
172
+ result["reasons"].append(f"Security validation error: {str(e)}")
173
+ return result
174
+
175
+ async def execute_secure_command(
176
+ self,
177
+ command: Union[str, List[str]],
178
+ working_dir: Optional[str] = None,
179
+ env_vars: Optional[Dict[str, str]] = None,
180
+ resource_limits: Optional[ResourceLimits] = None,
181
+ require_approval: bool = False,
182
+ ) -> Dict[str, Any]:
183
+ """Execute a command with full security validation and sandboxing."""
184
+
185
+ # Convert command to list if it's a string
186
+ if isinstance(command, str):
187
+ command_list = command.split()
188
+ else:
189
+ command_list = command
190
+ command = " ".join(command_list)
191
+
192
+ operation_id = f"cmd_{self.operation_counter}"
193
+ self.operation_counter += 1
194
+
195
+ result = {
196
+ "success": False,
197
+ "operation_id": operation_id,
198
+ "session_id": self.session_id,
199
+ "return_code": -1,
200
+ "stdout": "",
201
+ "stderr": "",
202
+ "security_info": {},
203
+ }
204
+
205
+ try:
206
+ # Create operation for validation
207
+ operation = PermissionOperation(
208
+ operation_type="command_execute",
209
+ command=command,
210
+ path=working_dir,
211
+ env_vars=env_vars,
212
+ require_approval=require_approval,
213
+ )
214
+
215
+ # Validate operation
216
+ validation_result = await self.validate_operation(operation)
217
+ result["security_info"] = validation_result
218
+
219
+ if not validation_result["allowed"]:
220
+ if self.audit:
221
+ self.audit.log_command_execution(
222
+ command,
223
+ success=False,
224
+ operation_id=operation_id,
225
+ session_id=self.session_id,
226
+ metadata={"validation_result": validation_result},
227
+ )
228
+
229
+ result["stderr"] = (
230
+ f"Command blocked: {', '.join(validation_result['reasons'])}"
231
+ )
232
+ return result
233
+
234
+ # Handle approval if required
235
+ if validation_result.get("approval_required"):
236
+ result["stderr"] = (
237
+ f"Approval required. Request ID: {validation_result['approval_request_id']}"
238
+ )
239
+ return result
240
+
241
+ # Execute command (with or without sandbox)
242
+ if validation_result.get("sandbox_required") and self.sandbox:
243
+ # Execute in sandbox
244
+ sandbox_result = self.sandbox.execute_sandboxed_command(
245
+ command_list,
246
+ working_dir=working_dir,
247
+ env_vars=env_vars,
248
+ limits=resource_limits,
249
+ )
250
+
251
+ result.update(
252
+ {
253
+ "success": sandbox_result["success"],
254
+ "return_code": sandbox_result["return_code"],
255
+ "stdout": sandbox_result["stdout"],
256
+ "stderr": sandbox_result["stderr"],
257
+ }
258
+ )
259
+ result["security_info"]["sandbox_dir"] = sandbox_result["sandbox_dir"]
260
+
261
+ else:
262
+ # Execute directly (not recommended for production)
263
+ import subprocess
264
+
265
+ try:
266
+ process = subprocess.run(
267
+ command_list,
268
+ cwd=working_dir,
269
+ env=env_vars,
270
+ capture_output=True,
271
+ text=True,
272
+ timeout=self.security_policies.get("max_command_timeout", 300),
273
+ )
274
+
275
+ result.update(
276
+ {
277
+ "success": process.returncode == 0,
278
+ "return_code": process.returncode,
279
+ "stdout": process.stdout,
280
+ "stderr": process.stderr,
281
+ }
282
+ )
283
+
284
+ except subprocess.TimeoutExpired:
285
+ result["stderr"] = "Command timed out"
286
+ except Exception as e:
287
+ result["stderr"] = f"Execution error: {str(e)}"
288
+
289
+ # Log the execution
290
+ if self.audit:
291
+ self.audit.log_command_execution(
292
+ command,
293
+ success=result["success"],
294
+ exit_code=result["return_code"],
295
+ operation_id=operation_id,
296
+ session_id=self.session_id,
297
+ sandbox_id=result["security_info"].get("sandbox_dir"),
298
+ metadata={"validation_result": validation_result},
299
+ )
300
+
301
+ return result
302
+
303
+ except Exception as e:
304
+ if self.audit:
305
+ self.audit.log_event(
306
+ AuditEventType.SECURITY_ALERT,
307
+ AuditLevel.ERROR,
308
+ f"Error executing secure command: {str(e)}",
309
+ operation_id=operation_id,
310
+ session_id=self.session_id,
311
+ metadata={"command": command, "error": str(e)},
312
+ )
313
+
314
+ result["stderr"] = f"Security manager error: {str(e)}"
315
+ return result
316
+
317
+ async def secure_file_access(
318
+ self, file_path: str, operation: str, content: Optional[str] = None
319
+ ) -> Dict[str, Any]:
320
+ """Securely access a file with full validation."""
321
+
322
+ operation_id = f"file_{self.operation_counter}"
323
+ self.operation_counter += 1
324
+
325
+ result = {
326
+ "success": False,
327
+ "operation_id": operation_id,
328
+ "session_id": self.session_id,
329
+ "content": None,
330
+ "error": None,
331
+ "security_info": {},
332
+ }
333
+
334
+ try:
335
+ # Create operation for validation
336
+ op = PermissionOperation(
337
+ operation_type=f"file_{operation}",
338
+ path=file_path,
339
+ content=content,
340
+ )
341
+
342
+ # Validate operation
343
+ validation_result = await self.validate_operation(op)
344
+ result["security_info"] = validation_result
345
+
346
+ if not validation_result["allowed"]:
347
+ if self.audit:
348
+ self.audit.log_file_access(
349
+ file_path,
350
+ operation,
351
+ allowed=False,
352
+ operation_id=operation_id,
353
+ session_id=self.session_id,
354
+ metadata={"validation_result": validation_result},
355
+ )
356
+
357
+ result["error"] = (
358
+ f"File access blocked: {', '.join(validation_result['reasons'])}"
359
+ )
360
+ return result
361
+
362
+ # Handle approval if required
363
+ if validation_result.get("approval_required"):
364
+ result["error"] = (
365
+ f"Approval required. Request ID: {validation_result['approval_request_id']}"
366
+ )
367
+ return result
368
+
369
+ # Perform file operation
370
+ file_path_obj = Path(file_path)
371
+
372
+ if operation == "read":
373
+ if file_path_obj.exists() and file_path_obj.is_file():
374
+ with open(file_path_obj, "r", encoding="utf-8") as f:
375
+ result["content"] = f.read()
376
+ result["success"] = True
377
+ else:
378
+ result["error"] = "File does not exist"
379
+
380
+ elif operation == "write":
381
+ if content is None:
382
+ result["error"] = "No content provided for write operation"
383
+ else:
384
+ # Check file size limits
385
+ max_size_mb = self.security_policies.get("max_file_size_mb", 100)
386
+ if len(content.encode("utf-8")) > max_size_mb * 1024 * 1024:
387
+ result["error"] = (
388
+ f"Content exceeds maximum file size ({max_size_mb}MB)"
389
+ )
390
+ else:
391
+ # Ensure directory exists
392
+ file_path_obj.parent.mkdir(parents=True, exist_ok=True)
393
+
394
+ with open(file_path_obj, "w", encoding="utf-8") as f:
395
+ f.write(content)
396
+ result["success"] = True
397
+
398
+ elif operation == "delete":
399
+ if file_path_obj.exists():
400
+ file_path_obj.unlink()
401
+ result["success"] = True
402
+ else:
403
+ result["error"] = "File does not exist"
404
+
405
+ else:
406
+ result["error"] = f"Unsupported file operation: {operation}"
407
+
408
+ # Log the file access
409
+ if self.audit:
410
+ self.audit.log_file_access(
411
+ file_path,
412
+ operation,
413
+ allowed=result["success"],
414
+ file_size=(len(content.encode("utf-8")) if content else None),
415
+ operation_id=operation_id,
416
+ session_id=self.session_id,
417
+ metadata={"validation_result": validation_result},
418
+ )
419
+
420
+ return result
421
+
422
+ except Exception as e:
423
+ if self.audit:
424
+ self.audit.log_event(
425
+ AuditEventType.SECURITY_ALERT,
426
+ AuditLevel.ERROR,
427
+ f"Error in secure file access: {str(e)}",
428
+ operation_id=operation_id,
429
+ session_id=self.session_id,
430
+ metadata={
431
+ "file_path": file_path,
432
+ "operation": operation,
433
+ "error": str(e),
434
+ },
435
+ )
436
+
437
+ result["error"] = f"Security manager error: {str(e)}"
438
+ return result
439
+
440
+ def get_restricted_paths(self) -> List[str]:
441
+ """Get list of restricted file paths."""
442
+ return self.permissions.get_restricted_paths()
443
+
444
+ def get_allowed_commands(self) -> List[str]:
445
+ """Get list of allowed commands."""
446
+ return self.permissions.get_allowed_commands()
447
+
448
+ def get_security_status(self) -> Dict[str, Any]:
449
+ """Get current security manager status."""
450
+
451
+ status = {
452
+ "session_id": self.session_id,
453
+ "components": {
454
+ "permissions": True,
455
+ "sandbox": self.sandbox is not None,
456
+ "approval_workflow": self.approval is not None,
457
+ "audit_logging": self.audit is not None,
458
+ },
459
+ "policies": self.security_policies,
460
+ "operation_count": self.operation_counter,
461
+ }
462
+
463
+ # Add component-specific status
464
+ if self.sandbox:
465
+ status["sandbox_status"] = {
466
+ "active_processes": self.sandbox.get_active_process_count(),
467
+ }
468
+
469
+ if self.approval:
470
+ status["approval_status"] = self.approval.get_approval_stats()
471
+
472
+ if self.audit:
473
+ status["audit_status"] = self.audit.get_statistics()
474
+
475
+ return status
476
+
477
+ def update_security_policy(self, policy_name: str, value: Any) -> bool:
478
+ """Update a security policy."""
479
+
480
+ if policy_name in self.security_policies:
481
+ old_value = self.security_policies[policy_name]
482
+ self.security_policies[policy_name] = value
483
+
484
+ if self.audit:
485
+ self.audit.log_event(
486
+ AuditEventType.SYSTEM_EVENT,
487
+ AuditLevel.INFO,
488
+ f"Security policy updated: {policy_name}",
489
+ session_id=self.session_id,
490
+ metadata={
491
+ "policy_name": policy_name,
492
+ "old_value": old_value,
493
+ "new_value": value,
494
+ },
495
+ )
496
+
497
+ return True
498
+
499
+ return False
500
+
501
+ async def shutdown(self) -> None:
502
+ """Shutdown the security manager gracefully."""
503
+
504
+ if self.audit:
505
+ self.audit.log_event(
506
+ AuditEventType.SYSTEM_EVENT,
507
+ AuditLevel.INFO,
508
+ "SecurityManager shutting down",
509
+ session_id=self.session_id,
510
+ metadata={"operation_count": self.operation_counter},
511
+ )
512
+
513
+ # Cleanup components
514
+ if self.sandbox:
515
+ self.sandbox.cleanup_all_sandboxes()
516
+
517
+ if self.approval:
518
+ # Cancel pending approvals
519
+ pending = self.approval.get_pending_requests()
520
+ for request in pending:
521
+ self.approval.cancel_request(request.id)
522
+
523
+ if self.audit:
524
+ self.audit.shutdown()
525
+
526
+ def __del__(self):
527
+ """Destructor to ensure proper cleanup."""
528
+ # Note: Cannot reliably run async cleanup in destructor
529
+ # Cleanup should be called explicitly via shutdown()
530
+ pass