PraisonAI 3.0.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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sandboxed Execution System for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Inspired by Codex CLI's sandbox modes for secure command execution.
|
|
5
|
+
Provides isolated execution environment when enabled via --sandbox flag.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
- SandboxExecutor: Base class for sandboxed execution
|
|
9
|
+
- SubprocessSandbox: Subprocess-based isolation with resource limits
|
|
10
|
+
- SandboxPolicy: Configurable security policies
|
|
11
|
+
|
|
12
|
+
Note: Sandbox is ONLY activated when explicitly requested via CLI flag.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Dict, List, Optional, Set
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from enum import Enum
|
|
19
|
+
import subprocess
|
|
20
|
+
import logging
|
|
21
|
+
import tempfile
|
|
22
|
+
import shutil
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# Sandbox Modes
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
class SandboxMode(Enum):
|
|
33
|
+
"""
|
|
34
|
+
Sandbox execution modes.
|
|
35
|
+
|
|
36
|
+
- DISABLED: No sandboxing (default)
|
|
37
|
+
- BASIC: Basic isolation with resource limits
|
|
38
|
+
- STRICT: Strict isolation with filesystem restrictions
|
|
39
|
+
- NETWORK_ISOLATED: No network access
|
|
40
|
+
"""
|
|
41
|
+
DISABLED = "disabled"
|
|
42
|
+
BASIC = "basic"
|
|
43
|
+
STRICT = "strict"
|
|
44
|
+
NETWORK_ISOLATED = "network_isolated"
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_string(cls, value: str) -> "SandboxMode":
|
|
48
|
+
"""Parse mode from string."""
|
|
49
|
+
value = value.lower().strip().replace("-", "_")
|
|
50
|
+
for mode in cls:
|
|
51
|
+
if mode.value == value:
|
|
52
|
+
return mode
|
|
53
|
+
raise ValueError(f"Unknown sandbox mode: {value}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ============================================================================
|
|
57
|
+
# Data Classes
|
|
58
|
+
# ============================================================================
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class SandboxPolicy:
|
|
62
|
+
"""
|
|
63
|
+
Security policy for sandbox execution.
|
|
64
|
+
"""
|
|
65
|
+
mode: SandboxMode = SandboxMode.DISABLED
|
|
66
|
+
|
|
67
|
+
# Resource limits
|
|
68
|
+
max_memory_mb: int = 512
|
|
69
|
+
max_cpu_seconds: int = 30
|
|
70
|
+
max_file_size_mb: int = 10
|
|
71
|
+
max_processes: int = 10
|
|
72
|
+
|
|
73
|
+
# Filesystem restrictions
|
|
74
|
+
allowed_paths: Set[str] = field(default_factory=set)
|
|
75
|
+
blocked_paths: Set[str] = field(default_factory=lambda: {
|
|
76
|
+
"/etc", "/var", "/usr", "/bin", "/sbin",
|
|
77
|
+
"/root", "/home", "/sys", "/proc"
|
|
78
|
+
})
|
|
79
|
+
temp_dir: Optional[str] = None
|
|
80
|
+
|
|
81
|
+
# Network restrictions
|
|
82
|
+
allow_network: bool = True
|
|
83
|
+
allowed_hosts: Set[str] = field(default_factory=set)
|
|
84
|
+
|
|
85
|
+
# Command restrictions
|
|
86
|
+
blocked_commands: Set[str] = field(default_factory=lambda: {
|
|
87
|
+
"rm", "rmdir", "mv", "dd", "mkfs", "fdisk",
|
|
88
|
+
"sudo", "su", "chmod", "chown", "kill", "pkill"
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def for_mode(cls, mode: SandboxMode) -> "SandboxPolicy":
|
|
93
|
+
"""Create policy for a given mode."""
|
|
94
|
+
if mode == SandboxMode.DISABLED:
|
|
95
|
+
return cls(mode=mode)
|
|
96
|
+
|
|
97
|
+
elif mode == SandboxMode.BASIC:
|
|
98
|
+
return cls(
|
|
99
|
+
mode=mode,
|
|
100
|
+
max_memory_mb=512,
|
|
101
|
+
max_cpu_seconds=60,
|
|
102
|
+
allow_network=True
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
elif mode == SandboxMode.STRICT:
|
|
106
|
+
return cls(
|
|
107
|
+
mode=mode,
|
|
108
|
+
max_memory_mb=256,
|
|
109
|
+
max_cpu_seconds=30,
|
|
110
|
+
max_processes=5,
|
|
111
|
+
allow_network=True,
|
|
112
|
+
blocked_commands={
|
|
113
|
+
"rm", "rmdir", "mv", "dd", "mkfs", "fdisk",
|
|
114
|
+
"sudo", "su", "chmod", "chown", "kill", "pkill",
|
|
115
|
+
"curl", "wget", "nc", "netcat", "ssh", "scp"
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
elif mode == SandboxMode.NETWORK_ISOLATED:
|
|
120
|
+
return cls(
|
|
121
|
+
mode=mode,
|
|
122
|
+
max_memory_mb=512,
|
|
123
|
+
max_cpu_seconds=60,
|
|
124
|
+
allow_network=False
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return cls(mode=mode)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class ExecutionResult:
|
|
132
|
+
"""Result of sandboxed execution."""
|
|
133
|
+
success: bool
|
|
134
|
+
exit_code: int
|
|
135
|
+
stdout: str
|
|
136
|
+
stderr: str
|
|
137
|
+
duration_ms: float
|
|
138
|
+
was_sandboxed: bool
|
|
139
|
+
policy_violations: List[str] = field(default_factory=list)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def output(self) -> str:
|
|
143
|
+
"""Get combined output."""
|
|
144
|
+
return self.stdout + self.stderr
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ============================================================================
|
|
148
|
+
# Command Validator
|
|
149
|
+
# ============================================================================
|
|
150
|
+
|
|
151
|
+
class CommandValidator:
|
|
152
|
+
"""
|
|
153
|
+
Validates commands against sandbox policy.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def __init__(self, policy: SandboxPolicy):
|
|
157
|
+
self.policy = policy
|
|
158
|
+
|
|
159
|
+
def validate(self, command: str) -> List[str]:
|
|
160
|
+
"""
|
|
161
|
+
Validate a command against the policy.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List of policy violations (empty if valid)
|
|
165
|
+
"""
|
|
166
|
+
violations = []
|
|
167
|
+
|
|
168
|
+
if self.policy.mode == SandboxMode.DISABLED:
|
|
169
|
+
return violations
|
|
170
|
+
|
|
171
|
+
# Parse command
|
|
172
|
+
parts = command.split()
|
|
173
|
+
if not parts:
|
|
174
|
+
return violations
|
|
175
|
+
|
|
176
|
+
cmd_name = Path(parts[0]).name
|
|
177
|
+
|
|
178
|
+
# Check blocked commands
|
|
179
|
+
if cmd_name in self.policy.blocked_commands:
|
|
180
|
+
violations.append(f"Command '{cmd_name}' is blocked by sandbox policy")
|
|
181
|
+
|
|
182
|
+
# Check for dangerous patterns
|
|
183
|
+
dangerous_patterns = [
|
|
184
|
+
("rm -rf", "Recursive force delete is blocked"),
|
|
185
|
+
("> /dev/", "Writing to device files is blocked"),
|
|
186
|
+
("| sh", "Piping to shell is blocked"),
|
|
187
|
+
("| bash", "Piping to bash is blocked"),
|
|
188
|
+
("$(", "Command substitution is blocked"),
|
|
189
|
+
("`", "Backtick command substitution is blocked"),
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
for pattern, message in dangerous_patterns:
|
|
193
|
+
if pattern in command:
|
|
194
|
+
violations.append(message)
|
|
195
|
+
|
|
196
|
+
# Check path access
|
|
197
|
+
for part in parts:
|
|
198
|
+
if part.startswith("/"):
|
|
199
|
+
for blocked in self.policy.blocked_paths:
|
|
200
|
+
if part.startswith(blocked):
|
|
201
|
+
violations.append(f"Access to '{blocked}' is blocked")
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
return violations
|
|
205
|
+
|
|
206
|
+
def is_allowed(self, command: str) -> bool:
|
|
207
|
+
"""Check if command is allowed."""
|
|
208
|
+
return len(self.validate(command)) == 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ============================================================================
|
|
212
|
+
# Subprocess Sandbox
|
|
213
|
+
# ============================================================================
|
|
214
|
+
|
|
215
|
+
class SubprocessSandbox:
|
|
216
|
+
"""
|
|
217
|
+
Subprocess-based sandbox execution.
|
|
218
|
+
|
|
219
|
+
Provides basic isolation through:
|
|
220
|
+
- Resource limits (timeout, memory via ulimit)
|
|
221
|
+
- Working directory isolation
|
|
222
|
+
- Environment variable control
|
|
223
|
+
- Command validation
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
policy: Optional[SandboxPolicy] = None,
|
|
229
|
+
working_dir: Optional[str] = None,
|
|
230
|
+
verbose: bool = False
|
|
231
|
+
):
|
|
232
|
+
self.policy = policy or SandboxPolicy()
|
|
233
|
+
self.working_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
234
|
+
self.verbose = verbose
|
|
235
|
+
self.validator = CommandValidator(self.policy)
|
|
236
|
+
|
|
237
|
+
self._temp_dir: Optional[Path] = None
|
|
238
|
+
|
|
239
|
+
def _setup_temp_dir(self) -> Path:
|
|
240
|
+
"""Set up temporary directory for isolated execution."""
|
|
241
|
+
if self._temp_dir is None:
|
|
242
|
+
self._temp_dir = Path(tempfile.mkdtemp(prefix="praisonai_sandbox_"))
|
|
243
|
+
return self._temp_dir
|
|
244
|
+
|
|
245
|
+
def _cleanup_temp_dir(self) -> None:
|
|
246
|
+
"""Clean up temporary directory."""
|
|
247
|
+
if self._temp_dir and self._temp_dir.exists():
|
|
248
|
+
try:
|
|
249
|
+
shutil.rmtree(self._temp_dir)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.debug(f"Failed to cleanup temp dir: {e}")
|
|
252
|
+
self._temp_dir = None
|
|
253
|
+
|
|
254
|
+
def _build_environment(self) -> Dict[str, str]:
|
|
255
|
+
"""Build restricted environment variables."""
|
|
256
|
+
import os
|
|
257
|
+
|
|
258
|
+
# Start with minimal environment
|
|
259
|
+
env = {
|
|
260
|
+
"PATH": "/usr/local/bin:/usr/bin:/bin",
|
|
261
|
+
"HOME": str(self._temp_dir or self.working_dir),
|
|
262
|
+
"TERM": "xterm",
|
|
263
|
+
"LANG": "en_US.UTF-8",
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Add some safe variables from current environment
|
|
267
|
+
safe_vars = ["USER", "SHELL", "PYTHONPATH"]
|
|
268
|
+
for var in safe_vars:
|
|
269
|
+
if var in os.environ:
|
|
270
|
+
env[var] = os.environ[var]
|
|
271
|
+
|
|
272
|
+
return env
|
|
273
|
+
|
|
274
|
+
def execute(
|
|
275
|
+
self,
|
|
276
|
+
command: str,
|
|
277
|
+
timeout: Optional[float] = None,
|
|
278
|
+
capture_output: bool = True
|
|
279
|
+
) -> ExecutionResult:
|
|
280
|
+
"""
|
|
281
|
+
Execute a command in the sandbox.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
command: Command to execute
|
|
285
|
+
timeout: Timeout in seconds (overrides policy)
|
|
286
|
+
capture_output: Whether to capture stdout/stderr
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
ExecutionResult with execution details
|
|
290
|
+
"""
|
|
291
|
+
start_time = time.time()
|
|
292
|
+
|
|
293
|
+
# Check if sandbox is enabled
|
|
294
|
+
if self.policy.mode == SandboxMode.DISABLED:
|
|
295
|
+
return self._execute_unsandboxed(command, timeout, capture_output)
|
|
296
|
+
|
|
297
|
+
# Validate command
|
|
298
|
+
violations = self.validator.validate(command)
|
|
299
|
+
if violations:
|
|
300
|
+
return ExecutionResult(
|
|
301
|
+
success=False,
|
|
302
|
+
exit_code=-1,
|
|
303
|
+
stdout="",
|
|
304
|
+
stderr="Command blocked by sandbox policy:\n" + "\n".join(violations),
|
|
305
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
306
|
+
was_sandboxed=True,
|
|
307
|
+
policy_violations=violations
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Set up sandbox
|
|
311
|
+
if self.policy.mode == SandboxMode.STRICT:
|
|
312
|
+
self._setup_temp_dir()
|
|
313
|
+
cwd = self._temp_dir
|
|
314
|
+
else:
|
|
315
|
+
cwd = self.working_dir
|
|
316
|
+
|
|
317
|
+
# Build environment
|
|
318
|
+
env = self._build_environment()
|
|
319
|
+
|
|
320
|
+
# Set timeout
|
|
321
|
+
if timeout is None:
|
|
322
|
+
timeout = float(self.policy.max_cpu_seconds)
|
|
323
|
+
|
|
324
|
+
# Execute
|
|
325
|
+
try:
|
|
326
|
+
result = subprocess.run(
|
|
327
|
+
command,
|
|
328
|
+
shell=True,
|
|
329
|
+
cwd=cwd,
|
|
330
|
+
env=env,
|
|
331
|
+
capture_output=capture_output,
|
|
332
|
+
text=True,
|
|
333
|
+
timeout=timeout
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return ExecutionResult(
|
|
337
|
+
success=result.returncode == 0,
|
|
338
|
+
exit_code=result.returncode,
|
|
339
|
+
stdout=result.stdout if capture_output else "",
|
|
340
|
+
stderr=result.stderr if capture_output else "",
|
|
341
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
342
|
+
was_sandboxed=True
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
except subprocess.TimeoutExpired:
|
|
346
|
+
return ExecutionResult(
|
|
347
|
+
success=False,
|
|
348
|
+
exit_code=-1,
|
|
349
|
+
stdout="",
|
|
350
|
+
stderr=f"Command timed out after {timeout}s",
|
|
351
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
352
|
+
was_sandboxed=True,
|
|
353
|
+
policy_violations=["Timeout exceeded"]
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
return ExecutionResult(
|
|
358
|
+
success=False,
|
|
359
|
+
exit_code=-1,
|
|
360
|
+
stdout="",
|
|
361
|
+
stderr=str(e),
|
|
362
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
363
|
+
was_sandboxed=True,
|
|
364
|
+
policy_violations=[str(e)]
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
finally:
|
|
368
|
+
if self.policy.mode == SandboxMode.STRICT:
|
|
369
|
+
self._cleanup_temp_dir()
|
|
370
|
+
|
|
371
|
+
def _execute_unsandboxed(
|
|
372
|
+
self,
|
|
373
|
+
command: str,
|
|
374
|
+
timeout: Optional[float],
|
|
375
|
+
capture_output: bool
|
|
376
|
+
) -> ExecutionResult:
|
|
377
|
+
"""Execute without sandbox (when disabled)."""
|
|
378
|
+
start_time = time.time()
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
result = subprocess.run(
|
|
382
|
+
command,
|
|
383
|
+
shell=True,
|
|
384
|
+
cwd=self.working_dir,
|
|
385
|
+
capture_output=capture_output,
|
|
386
|
+
text=True,
|
|
387
|
+
timeout=timeout
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return ExecutionResult(
|
|
391
|
+
success=result.returncode == 0,
|
|
392
|
+
exit_code=result.returncode,
|
|
393
|
+
stdout=result.stdout if capture_output else "",
|
|
394
|
+
stderr=result.stderr if capture_output else "",
|
|
395
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
396
|
+
was_sandboxed=False
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
except subprocess.TimeoutExpired:
|
|
400
|
+
return ExecutionResult(
|
|
401
|
+
success=False,
|
|
402
|
+
exit_code=-1,
|
|
403
|
+
stdout="",
|
|
404
|
+
stderr=f"Command timed out after {timeout}s",
|
|
405
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
406
|
+
was_sandboxed=False
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
except Exception as e:
|
|
410
|
+
return ExecutionResult(
|
|
411
|
+
success=False,
|
|
412
|
+
exit_code=-1,
|
|
413
|
+
stdout="",
|
|
414
|
+
stderr=str(e),
|
|
415
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
416
|
+
was_sandboxed=False
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# ============================================================================
|
|
421
|
+
# CLI Integration Handler
|
|
422
|
+
# ============================================================================
|
|
423
|
+
|
|
424
|
+
class SandboxExecutorHandler:
|
|
425
|
+
"""
|
|
426
|
+
Handler for integrating Sandbox Execution with PraisonAI CLI.
|
|
427
|
+
|
|
428
|
+
Note: Sandbox is ONLY activated when --sandbox flag is passed.
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
def __init__(self, verbose: bool = False):
|
|
432
|
+
self.verbose = verbose
|
|
433
|
+
self._sandbox: Optional[SubprocessSandbox] = None
|
|
434
|
+
self._enabled: bool = False
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def feature_name(self) -> str:
|
|
438
|
+
return "sandbox_executor"
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def is_enabled(self) -> bool:
|
|
442
|
+
"""Check if sandbox is enabled."""
|
|
443
|
+
return self._enabled
|
|
444
|
+
|
|
445
|
+
def initialize(
|
|
446
|
+
self,
|
|
447
|
+
mode: str = "disabled",
|
|
448
|
+
working_dir: Optional[str] = None,
|
|
449
|
+
policy: Optional[SandboxPolicy] = None
|
|
450
|
+
) -> SubprocessSandbox:
|
|
451
|
+
"""
|
|
452
|
+
Initialize the sandbox.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
mode: Sandbox mode (disabled, basic, strict, network_isolated)
|
|
456
|
+
working_dir: Working directory for execution
|
|
457
|
+
policy: Optional custom policy
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Configured SubprocessSandbox
|
|
461
|
+
"""
|
|
462
|
+
try:
|
|
463
|
+
sandbox_mode = SandboxMode.from_string(mode)
|
|
464
|
+
except ValueError:
|
|
465
|
+
logger.warning(f"Unknown sandbox mode '{mode}', defaulting to 'disabled'")
|
|
466
|
+
sandbox_mode = SandboxMode.DISABLED
|
|
467
|
+
|
|
468
|
+
if policy is None:
|
|
469
|
+
policy = SandboxPolicy.for_mode(sandbox_mode)
|
|
470
|
+
|
|
471
|
+
self._sandbox = SubprocessSandbox(
|
|
472
|
+
policy=policy,
|
|
473
|
+
working_dir=working_dir,
|
|
474
|
+
verbose=self.verbose
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
self._enabled = sandbox_mode != SandboxMode.DISABLED
|
|
478
|
+
|
|
479
|
+
if self.verbose and self._enabled:
|
|
480
|
+
from rich import print as rprint
|
|
481
|
+
rprint(f"[yellow]⚠ Sandbox enabled: {sandbox_mode.value}[/yellow]")
|
|
482
|
+
|
|
483
|
+
return self._sandbox
|
|
484
|
+
|
|
485
|
+
def get_sandbox(self) -> Optional[SubprocessSandbox]:
|
|
486
|
+
"""Get the sandbox executor."""
|
|
487
|
+
return self._sandbox
|
|
488
|
+
|
|
489
|
+
def execute(
|
|
490
|
+
self,
|
|
491
|
+
command: str,
|
|
492
|
+
timeout: Optional[float] = None
|
|
493
|
+
) -> ExecutionResult:
|
|
494
|
+
"""
|
|
495
|
+
Execute a command.
|
|
496
|
+
|
|
497
|
+
If sandbox is not initialized, runs without sandboxing.
|
|
498
|
+
"""
|
|
499
|
+
if not self._sandbox:
|
|
500
|
+
self._sandbox = self.initialize()
|
|
501
|
+
|
|
502
|
+
return self._sandbox.execute(command, timeout=timeout)
|
|
503
|
+
|
|
504
|
+
def validate_command(self, command: str) -> List[str]:
|
|
505
|
+
"""Validate a command against sandbox policy."""
|
|
506
|
+
if not self._sandbox:
|
|
507
|
+
return []
|
|
508
|
+
|
|
509
|
+
return self._sandbox.validator.validate(command)
|
|
510
|
+
|
|
511
|
+
def get_mode(self) -> str:
|
|
512
|
+
"""Get current sandbox mode."""
|
|
513
|
+
if self._sandbox:
|
|
514
|
+
return self._sandbox.policy.mode.value
|
|
515
|
+
return SandboxMode.DISABLED.value
|