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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Sandbox manager for isolating and limiting agent operations."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import resource
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
import psutil
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ResourceLimits:
|
|
17
|
+
"""Resource limits for sandboxed operations."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
max_memory_mb: int = 512,
|
|
22
|
+
max_cpu_seconds: int = 30,
|
|
23
|
+
max_file_size_mb: int = 100,
|
|
24
|
+
max_open_files: int = 100,
|
|
25
|
+
max_processes: int = 10,
|
|
26
|
+
timeout_seconds: int = 60,
|
|
27
|
+
):
|
|
28
|
+
self.max_memory_mb = max_memory_mb
|
|
29
|
+
self.max_cpu_seconds = max_cpu_seconds
|
|
30
|
+
self.max_file_size_mb = max_file_size_mb
|
|
31
|
+
self.max_open_files = max_open_files
|
|
32
|
+
self.max_processes = max_processes
|
|
33
|
+
self.timeout_seconds = timeout_seconds
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SandboxedProcess:
|
|
37
|
+
"""Represents a process running in a sandbox."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self, process: subprocess.Popen, temp_dir: str, limits: ResourceLimits
|
|
41
|
+
):
|
|
42
|
+
self.process = process
|
|
43
|
+
self.temp_dir = temp_dir
|
|
44
|
+
self.limits = limits
|
|
45
|
+
self.start_time = time.time()
|
|
46
|
+
self.monitor_thread = None
|
|
47
|
+
self.terminated = False
|
|
48
|
+
|
|
49
|
+
def is_running(self) -> bool:
|
|
50
|
+
"""Check if process is still running."""
|
|
51
|
+
return self.process.poll() is None and not self.terminated
|
|
52
|
+
|
|
53
|
+
def terminate(self) -> None:
|
|
54
|
+
"""Terminate the sandboxed process."""
|
|
55
|
+
if not self.terminated:
|
|
56
|
+
self.terminated = True
|
|
57
|
+
try:
|
|
58
|
+
if self.process.poll() is None:
|
|
59
|
+
self.process.terminate()
|
|
60
|
+
# Give it a moment to terminate gracefully
|
|
61
|
+
try:
|
|
62
|
+
self.process.wait(timeout=5)
|
|
63
|
+
except subprocess.TimeoutExpired:
|
|
64
|
+
self.process.kill()
|
|
65
|
+
except (ProcessLookupError, OSError):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def cleanup(self) -> None:
|
|
69
|
+
"""Clean up temporary resources."""
|
|
70
|
+
self.terminate()
|
|
71
|
+
if os.path.exists(self.temp_dir):
|
|
72
|
+
try:
|
|
73
|
+
shutil.rmtree(self.temp_dir)
|
|
74
|
+
except (OSError, PermissionError):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SandboxManager:
|
|
79
|
+
"""Manages sandboxed execution of agent operations."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, default_limits: Optional[ResourceLimits] = None):
|
|
82
|
+
self.default_limits = default_limits or ResourceLimits()
|
|
83
|
+
self.active_processes: Dict[int, SandboxedProcess] = {}
|
|
84
|
+
self.temp_base_dir = tempfile.gettempdir()
|
|
85
|
+
|
|
86
|
+
def create_sandbox_environment(
|
|
87
|
+
self, limits: Optional[ResourceLimits] = None
|
|
88
|
+
) -> str:
|
|
89
|
+
"""Create a temporary sandbox directory."""
|
|
90
|
+
limits = limits or self.default_limits
|
|
91
|
+
|
|
92
|
+
# Create temporary directory for sandbox
|
|
93
|
+
temp_dir = tempfile.mkdtemp(prefix="omnimancer_sandbox_")
|
|
94
|
+
|
|
95
|
+
# Set up basic directory structure
|
|
96
|
+
os.makedirs(os.path.join(temp_dir, "workspace"), exist_ok=True)
|
|
97
|
+
os.makedirs(os.path.join(temp_dir, "output"), exist_ok=True)
|
|
98
|
+
|
|
99
|
+
# Create sandbox info file
|
|
100
|
+
info_file = os.path.join(temp_dir, "sandbox_info.txt")
|
|
101
|
+
with open(info_file, "w") as f:
|
|
102
|
+
f.write(f"Sandbox created at: {time.ctime()}\n")
|
|
103
|
+
f.write(f"Memory limit: {limits.max_memory_mb} MB\n")
|
|
104
|
+
f.write(f"CPU limit: {limits.max_cpu_seconds} seconds\n")
|
|
105
|
+
f.write(f"Timeout: {limits.timeout_seconds} seconds\n")
|
|
106
|
+
|
|
107
|
+
return temp_dir
|
|
108
|
+
|
|
109
|
+
def execute_sandboxed_command(
|
|
110
|
+
self,
|
|
111
|
+
command: List[str],
|
|
112
|
+
working_dir: Optional[str] = None,
|
|
113
|
+
env_vars: Optional[Dict[str, str]] = None,
|
|
114
|
+
limits: Optional[ResourceLimits] = None,
|
|
115
|
+
input_data: Optional[str] = None,
|
|
116
|
+
) -> Dict[str, Any]:
|
|
117
|
+
"""Execute a command in a sandboxed environment."""
|
|
118
|
+
|
|
119
|
+
limits = limits or self.default_limits
|
|
120
|
+
sandbox_dir = self.create_sandbox_environment(limits)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
# Prepare environment
|
|
124
|
+
sandbox_env = os.environ.copy()
|
|
125
|
+
if env_vars:
|
|
126
|
+
sandbox_env.update(env_vars)
|
|
127
|
+
|
|
128
|
+
# Restrict environment variables
|
|
129
|
+
sandbox_env = self._filter_environment_variables(sandbox_env)
|
|
130
|
+
|
|
131
|
+
# Set working directory
|
|
132
|
+
work_dir = working_dir or os.path.join(sandbox_dir, "workspace")
|
|
133
|
+
|
|
134
|
+
# Prepare process arguments
|
|
135
|
+
process_args = {
|
|
136
|
+
"args": command,
|
|
137
|
+
"cwd": work_dir,
|
|
138
|
+
"env": sandbox_env,
|
|
139
|
+
"stdin": subprocess.PIPE if input_data else None,
|
|
140
|
+
"stdout": subprocess.PIPE,
|
|
141
|
+
"stderr": subprocess.PIPE,
|
|
142
|
+
"text": True,
|
|
143
|
+
"preexec_fn": self._setup_process_limits(limits),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Start process
|
|
147
|
+
process = subprocess.Popen(**process_args)
|
|
148
|
+
sandboxed_proc = SandboxedProcess(process, sandbox_dir, limits)
|
|
149
|
+
|
|
150
|
+
# Track the process
|
|
151
|
+
self.active_processes[process.pid] = sandboxed_proc
|
|
152
|
+
|
|
153
|
+
# Start monitoring
|
|
154
|
+
sandboxed_proc.monitor_thread = threading.Thread(
|
|
155
|
+
target=self._monitor_process, args=(sandboxed_proc,)
|
|
156
|
+
)
|
|
157
|
+
sandboxed_proc.monitor_thread.start()
|
|
158
|
+
|
|
159
|
+
# Execute and wait for completion
|
|
160
|
+
try:
|
|
161
|
+
stdout, stderr = process.communicate(
|
|
162
|
+
input=input_data, timeout=limits.timeout_seconds
|
|
163
|
+
)
|
|
164
|
+
return_code = process.returncode
|
|
165
|
+
|
|
166
|
+
except subprocess.TimeoutExpired:
|
|
167
|
+
sandboxed_proc.terminate()
|
|
168
|
+
return {
|
|
169
|
+
"success": False,
|
|
170
|
+
"return_code": -1,
|
|
171
|
+
"stdout": "",
|
|
172
|
+
"stderr": "Command timed out",
|
|
173
|
+
"sandbox_dir": sandbox_dir,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
"success": return_code == 0,
|
|
178
|
+
"return_code": return_code,
|
|
179
|
+
"stdout": stdout or "",
|
|
180
|
+
"stderr": stderr or "",
|
|
181
|
+
"sandbox_dir": sandbox_dir,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
return {
|
|
186
|
+
"success": False,
|
|
187
|
+
"return_code": -1,
|
|
188
|
+
"stdout": "",
|
|
189
|
+
"stderr": f"Sandbox execution error: {str(e)}",
|
|
190
|
+
"sandbox_dir": sandbox_dir,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
finally:
|
|
194
|
+
# Clean up
|
|
195
|
+
if process.pid in self.active_processes:
|
|
196
|
+
self.active_processes[process.pid].cleanup()
|
|
197
|
+
del self.active_processes[process.pid]
|
|
198
|
+
|
|
199
|
+
def _setup_process_limits(self, limits: ResourceLimits) -> Callable:
|
|
200
|
+
"""Create a function to set up process resource limits."""
|
|
201
|
+
|
|
202
|
+
def setup_limits():
|
|
203
|
+
try:
|
|
204
|
+
# Set memory limit (in bytes)
|
|
205
|
+
memory_limit = limits.max_memory_mb * 1024 * 1024
|
|
206
|
+
resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
|
|
207
|
+
|
|
208
|
+
# Set CPU time limit (in seconds)
|
|
209
|
+
resource.setrlimit(
|
|
210
|
+
resource.RLIMIT_CPU,
|
|
211
|
+
(limits.max_cpu_seconds, limits.max_cpu_seconds),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Set file size limit (in bytes)
|
|
215
|
+
file_size_limit = limits.max_file_size_mb * 1024 * 1024
|
|
216
|
+
resource.setrlimit(
|
|
217
|
+
resource.RLIMIT_FSIZE, (file_size_limit, file_size_limit)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Set maximum number of open files
|
|
221
|
+
resource.setrlimit(
|
|
222
|
+
resource.RLIMIT_NOFILE,
|
|
223
|
+
(limits.max_open_files, limits.max_open_files),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Set maximum number of processes
|
|
227
|
+
resource.setrlimit(
|
|
228
|
+
resource.RLIMIT_NPROC,
|
|
229
|
+
(limits.max_processes, limits.max_processes),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
except (OSError, ValueError) as e:
|
|
233
|
+
# Log the error but don't fail the process creation
|
|
234
|
+
print(f"Warning: Could not set all resource limits: {e}")
|
|
235
|
+
|
|
236
|
+
return setup_limits
|
|
237
|
+
|
|
238
|
+
def _monitor_process(self, sandboxed_proc: SandboxedProcess) -> None:
|
|
239
|
+
"""Monitor a sandboxed process for resource violations."""
|
|
240
|
+
|
|
241
|
+
while sandboxed_proc.is_running():
|
|
242
|
+
try:
|
|
243
|
+
# Check if process still exists
|
|
244
|
+
if sandboxed_proc.process.poll() is not None:
|
|
245
|
+
break
|
|
246
|
+
|
|
247
|
+
# Get process info
|
|
248
|
+
try:
|
|
249
|
+
proc_info = psutil.Process(sandboxed_proc.process.pid)
|
|
250
|
+
|
|
251
|
+
# Check memory usage
|
|
252
|
+
memory_mb = proc_info.memory_info().rss / (1024 * 1024)
|
|
253
|
+
if memory_mb > sandboxed_proc.limits.max_memory_mb:
|
|
254
|
+
print(
|
|
255
|
+
f"Process {sandboxed_proc.process.pid} exceeded memory limit"
|
|
256
|
+
)
|
|
257
|
+
sandboxed_proc.terminate()
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
# Check CPU time
|
|
261
|
+
cpu_time = proc_info.cpu_times().user + proc_info.cpu_times().system
|
|
262
|
+
if cpu_time > sandboxed_proc.limits.max_cpu_seconds:
|
|
263
|
+
print(
|
|
264
|
+
f"Process {sandboxed_proc.process.pid} exceeded CPU time limit"
|
|
265
|
+
)
|
|
266
|
+
sandboxed_proc.terminate()
|
|
267
|
+
break
|
|
268
|
+
|
|
269
|
+
# Check total runtime
|
|
270
|
+
runtime = time.time() - sandboxed_proc.start_time
|
|
271
|
+
if runtime > sandboxed_proc.limits.timeout_seconds:
|
|
272
|
+
print(
|
|
273
|
+
f"Process {sandboxed_proc.process.pid} exceeded runtime limit"
|
|
274
|
+
)
|
|
275
|
+
sandboxed_proc.terminate()
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
except psutil.NoSuchProcess:
|
|
279
|
+
# Process already terminated
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
# Sleep before next check
|
|
283
|
+
time.sleep(1.0)
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
print(f"Error monitoring process: {e}")
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
def _filter_environment_variables(self, env: Dict[str, str]) -> Dict[str, str]:
|
|
290
|
+
"""Filter environment variables to remove sensitive ones."""
|
|
291
|
+
|
|
292
|
+
# List of sensitive environment variable patterns
|
|
293
|
+
sensitive_patterns = [
|
|
294
|
+
"PASSWORD",
|
|
295
|
+
"SECRET",
|
|
296
|
+
"TOKEN",
|
|
297
|
+
"KEY",
|
|
298
|
+
"CREDENTIAL",
|
|
299
|
+
"AWS_",
|
|
300
|
+
"AZURE_",
|
|
301
|
+
"GCP_",
|
|
302
|
+
"GOOGLE_",
|
|
303
|
+
"SSH_",
|
|
304
|
+
"HOME",
|
|
305
|
+
"USER",
|
|
306
|
+
"USERNAME",
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
filtered_env = {}
|
|
310
|
+
for key, value in env.items():
|
|
311
|
+
# Keep only safe environment variables
|
|
312
|
+
if not any(pattern in key.upper() for pattern in sensitive_patterns):
|
|
313
|
+
filtered_env[key] = value
|
|
314
|
+
|
|
315
|
+
# Add minimal required variables
|
|
316
|
+
filtered_env.update(
|
|
317
|
+
{
|
|
318
|
+
"PATH": "/usr/local/bin:/usr/bin:/bin",
|
|
319
|
+
"LANG": "en_US.UTF-8",
|
|
320
|
+
"LC_ALL": "en_US.UTF-8",
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return filtered_env
|
|
325
|
+
|
|
326
|
+
@contextmanager
|
|
327
|
+
def sandbox_context(self, limits: Optional[ResourceLimits] = None):
|
|
328
|
+
"""Context manager for sandbox operations."""
|
|
329
|
+
|
|
330
|
+
sandbox_dir = self.create_sandbox_environment(limits)
|
|
331
|
+
try:
|
|
332
|
+
yield sandbox_dir
|
|
333
|
+
finally:
|
|
334
|
+
if os.path.exists(sandbox_dir):
|
|
335
|
+
try:
|
|
336
|
+
shutil.rmtree(sandbox_dir)
|
|
337
|
+
except (OSError, PermissionError):
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
def cleanup_all_sandboxes(self) -> None:
|
|
341
|
+
"""Clean up all active sandboxes."""
|
|
342
|
+
|
|
343
|
+
for proc in list(self.active_processes.values()):
|
|
344
|
+
proc.cleanup()
|
|
345
|
+
self.active_processes.clear()
|
|
346
|
+
|
|
347
|
+
def get_active_process_count(self) -> int:
|
|
348
|
+
"""Get number of active sandboxed processes."""
|
|
349
|
+
return len(self.active_processes)
|
|
350
|
+
|
|
351
|
+
def get_sandbox_info(self, process_id: int) -> Optional[Dict[str, Any]]:
|
|
352
|
+
"""Get information about a sandboxed process."""
|
|
353
|
+
|
|
354
|
+
if process_id not in self.active_processes:
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
proc = self.active_processes[process_id]
|
|
358
|
+
try:
|
|
359
|
+
proc_info = psutil.Process(process_id)
|
|
360
|
+
return {
|
|
361
|
+
"pid": process_id,
|
|
362
|
+
"is_running": proc.is_running(),
|
|
363
|
+
"start_time": proc.start_time,
|
|
364
|
+
"runtime": time.time() - proc.start_time,
|
|
365
|
+
"memory_mb": proc_info.memory_info().rss / (1024 * 1024),
|
|
366
|
+
"cpu_percent": proc_info.cpu_percent(),
|
|
367
|
+
"sandbox_dir": proc.temp_dir,
|
|
368
|
+
"limits": {
|
|
369
|
+
"max_memory_mb": proc.limits.max_memory_mb,
|
|
370
|
+
"max_cpu_seconds": proc.limits.max_cpu_seconds,
|
|
371
|
+
"timeout_seconds": proc.limits.timeout_seconds,
|
|
372
|
+
},
|
|
373
|
+
}
|
|
374
|
+
except psutil.NoSuchProcess:
|
|
375
|
+
return {
|
|
376
|
+
"pid": process_id,
|
|
377
|
+
"is_running": False,
|
|
378
|
+
"sandbox_dir": proc.temp_dir,
|
|
379
|
+
}
|