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.
Files changed (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. 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