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,546 @@
1
+ """
2
+ Action Orchestrator for PraisonAI.
3
+
4
+ Orchestrates code modifications through ACP with plan → approval → apply → verify steps.
5
+ Enforces read-only mode when ACP is unavailable.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from .interactive_runtime import InteractiveRuntime
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ActionType(Enum):
22
+ """Types of actions that can be orchestrated."""
23
+ FILE_CREATE = "file_create"
24
+ FILE_EDIT = "file_edit"
25
+ FILE_DELETE = "file_delete"
26
+ FILE_RENAME = "file_rename"
27
+ SHELL_COMMAND = "shell_command"
28
+ REFACTOR = "refactor"
29
+ UNKNOWN = "unknown"
30
+
31
+
32
+ class ActionStatus(Enum):
33
+ """Status of an action."""
34
+ PENDING = "pending"
35
+ APPROVED = "approved"
36
+ REJECTED = "rejected"
37
+ APPLIED = "applied"
38
+ FAILED = "failed"
39
+ VERIFIED = "verified"
40
+
41
+
42
+ @dataclass
43
+ class ActionStep:
44
+ """A single step in an action plan."""
45
+ id: str
46
+ action_type: ActionType
47
+ description: str
48
+ target: str # File path or command
49
+ params: Dict[str, Any] = field(default_factory=dict)
50
+ status: ActionStatus = ActionStatus.PENDING
51
+ result: Optional[Any] = None
52
+ error: Optional[str] = None
53
+
54
+
55
+ @dataclass
56
+ class ActionPlan:
57
+ """A plan of actions to execute."""
58
+ id: str
59
+ prompt: str
60
+ steps: List[ActionStep] = field(default_factory=list)
61
+ created_at: float = field(default_factory=time.time)
62
+ status: ActionStatus = ActionStatus.PENDING
63
+ approval_mode: str = "manual"
64
+
65
+ def to_dict(self) -> Dict[str, Any]:
66
+ return {
67
+ "id": self.id,
68
+ "prompt": self.prompt,
69
+ "steps": [
70
+ {
71
+ "id": s.id,
72
+ "action_type": s.action_type.value,
73
+ "description": s.description,
74
+ "target": s.target,
75
+ "params": s.params,
76
+ "status": s.status.value,
77
+ "result": s.result,
78
+ "error": s.error
79
+ }
80
+ for s in self.steps
81
+ ],
82
+ "created_at": self.created_at,
83
+ "status": self.status.value,
84
+ "approval_mode": self.approval_mode
85
+ }
86
+
87
+
88
+ @dataclass
89
+ class ActionResult:
90
+ """Result of action orchestration."""
91
+ success: bool
92
+ plan: Optional[ActionPlan] = None
93
+ applied_steps: int = 0
94
+ failed_steps: int = 0
95
+ error: Optional[str] = None
96
+ read_only_blocked: bool = False
97
+ diff_summary: Optional[str] = None
98
+
99
+ def to_dict(self) -> Dict[str, Any]:
100
+ return {
101
+ "success": self.success,
102
+ "plan": self.plan.to_dict() if self.plan else None,
103
+ "applied_steps": self.applied_steps,
104
+ "failed_steps": self.failed_steps,
105
+ "error": self.error,
106
+ "read_only_blocked": self.read_only_blocked,
107
+ "diff_summary": self.diff_summary
108
+ }
109
+
110
+
111
+ class ActionOrchestrator:
112
+ """
113
+ Orchestrates code modifications through ACP.
114
+
115
+ All edits must go through:
116
+ 1. Plan creation
117
+ 2. Approval (manual, auto, or scoped)
118
+ 3. Application
119
+ 4. Verification
120
+
121
+ If ACP is unavailable, runtime is read-only and edits are blocked.
122
+ """
123
+
124
+ def __init__(self, runtime: "InteractiveRuntime"):
125
+ """Initialize with runtime reference."""
126
+ self.runtime = runtime
127
+ self._plan_counter = 0
128
+
129
+ def _generate_plan_id(self) -> str:
130
+ """Generate a unique plan ID."""
131
+ self._plan_counter += 1
132
+ return f"plan_{int(time.time())}_{self._plan_counter}"
133
+
134
+ def _generate_step_id(self, plan_id: str, index: int) -> str:
135
+ """Generate a unique step ID."""
136
+ return f"{plan_id}_step_{index}"
137
+
138
+ async def create_plan(self, prompt: str) -> ActionResult:
139
+ """
140
+ Create an action plan for a modification request.
141
+
142
+ Args:
143
+ prompt: The user's modification request
144
+
145
+ Returns:
146
+ ActionResult with the plan
147
+ """
148
+ if self.runtime.read_only:
149
+ return ActionResult(
150
+ success=False,
151
+ error="Runtime is in read-only mode. ACP is required for modifications.",
152
+ read_only_blocked=True
153
+ )
154
+
155
+ plan_id = self._generate_plan_id()
156
+
157
+ # Always use fallback plan creation (ACP session tracks but doesn't create plans)
158
+ # In production, this would integrate with an LLM for intelligent planning
159
+ return await self._create_fallback_plan(prompt, plan_id)
160
+
161
+ async def _create_fallback_plan(self, prompt: str, plan_id: str) -> ActionResult:
162
+ """Create a fallback plan when ACP is not available for planning."""
163
+ # This is a simplified fallback - in production, this would use LLM
164
+ steps = []
165
+
166
+ # Analyze prompt for common patterns
167
+ prompt_lower = prompt.lower()
168
+
169
+ if "create" in prompt_lower or "new file" in prompt_lower:
170
+ # Extract file path if mentioned
171
+ import re
172
+ file_match = re.search(r'(?:create|new)\s+(?:a\s+)?(?:file\s+)?(?:called\s+)?([^\s]+\.\w+)', prompt_lower)
173
+ target = file_match.group(1) if file_match else "new_file.py"
174
+
175
+ # Generate default content based on file type
176
+ if target.endswith('.py'):
177
+ content = f'"""\n{target} - Auto-generated file\n"""\n\ndef main():\n pass\n\n\nif __name__ == "__main__":\n main()\n'
178
+ else:
179
+ content = f"// {target} - Auto-generated file\n"
180
+
181
+ steps.append(ActionStep(
182
+ id=self._generate_step_id(plan_id, 0),
183
+ action_type=ActionType.FILE_CREATE,
184
+ description=f"Create file: {target}",
185
+ target=target,
186
+ params={"content": content},
187
+ status=ActionStatus.APPROVED # Auto-approve for fallback
188
+ ))
189
+
190
+ elif "edit" in prompt_lower or "modify" in prompt_lower or "change" in prompt_lower:
191
+ file_match = re.search(r'(?:edit|modify|change)\s+([^\s]+\.\w+)', prompt_lower)
192
+ target = file_match.group(1) if file_match else ""
193
+
194
+ if target:
195
+ steps.append(ActionStep(
196
+ id=self._generate_step_id(plan_id, 0),
197
+ action_type=ActionType.FILE_EDIT,
198
+ description=f"Edit file: {target}",
199
+ target=target,
200
+ params={"changes": prompt}
201
+ ))
202
+
203
+ elif "delete" in prompt_lower or "remove" in prompt_lower:
204
+ file_match = re.search(r'(?:delete|remove)\s+([^\s]+\.\w+)', prompt_lower)
205
+ target = file_match.group(1) if file_match else ""
206
+
207
+ if target:
208
+ steps.append(ActionStep(
209
+ id=self._generate_step_id(plan_id, 0),
210
+ action_type=ActionType.FILE_DELETE,
211
+ description=f"Delete file: {target}",
212
+ target=target
213
+ ))
214
+
215
+ elif "rename" in prompt_lower:
216
+ steps.append(ActionStep(
217
+ id=self._generate_step_id(plan_id, 0),
218
+ action_type=ActionType.REFACTOR,
219
+ description="Rename operation",
220
+ target="",
221
+ params={"prompt": prompt}
222
+ ))
223
+
224
+ elif "run" in prompt_lower or "execute" in prompt_lower:
225
+ cmd_match = re.search(r'(?:run|execute)\s+[`"\']?([^`"\']+)[`"\']?', prompt_lower)
226
+ command = cmd_match.group(1) if cmd_match else ""
227
+
228
+ if command:
229
+ steps.append(ActionStep(
230
+ id=self._generate_step_id(plan_id, 0),
231
+ action_type=ActionType.SHELL_COMMAND,
232
+ description=f"Execute: {command}",
233
+ target=command
234
+ ))
235
+
236
+ if not steps:
237
+ return ActionResult(
238
+ success=False,
239
+ error="Could not determine action from prompt. Please be more specific."
240
+ )
241
+
242
+ plan = ActionPlan(
243
+ id=plan_id,
244
+ prompt=prompt,
245
+ steps=steps,
246
+ approval_mode=self.runtime.config.approval_mode
247
+ )
248
+
249
+ return ActionResult(
250
+ success=True,
251
+ plan=plan
252
+ )
253
+
254
+ def _classify_action_type(self, step_data: Dict[str, Any]) -> ActionType:
255
+ """Classify the action type from step data."""
256
+ action = step_data.get("action", "").lower()
257
+
258
+ if "create" in action:
259
+ return ActionType.FILE_CREATE
260
+ elif "edit" in action or "modify" in action:
261
+ return ActionType.FILE_EDIT
262
+ elif "delete" in action or "remove" in action:
263
+ return ActionType.FILE_DELETE
264
+ elif "rename" in action:
265
+ return ActionType.FILE_RENAME
266
+ elif "shell" in action or "command" in action or "run" in action:
267
+ return ActionType.SHELL_COMMAND
268
+ elif "refactor" in action:
269
+ return ActionType.REFACTOR
270
+
271
+ return ActionType.UNKNOWN
272
+
273
+ async def approve_plan(self, plan: ActionPlan, auto: bool = False) -> bool:
274
+ """
275
+ Request approval for a plan.
276
+
277
+ Args:
278
+ plan: The plan to approve
279
+ auto: Whether to auto-approve
280
+
281
+ Returns:
282
+ True if approved, False if rejected
283
+ """
284
+ if self.runtime.read_only:
285
+ logger.warning("Cannot approve plan in read-only mode")
286
+ return False
287
+
288
+ approval_mode = self.runtime.config.approval_mode
289
+
290
+ if approval_mode == "auto" or auto:
291
+ plan.status = ActionStatus.APPROVED
292
+ for step in plan.steps:
293
+ step.status = ActionStatus.APPROVED
294
+ return True
295
+
296
+ elif approval_mode == "scoped":
297
+ # Auto-approve safe actions, require manual for dangerous ones
298
+ dangerous_types = {ActionType.FILE_DELETE, ActionType.SHELL_COMMAND}
299
+
300
+ for step in plan.steps:
301
+ if step.action_type in dangerous_types:
302
+ step.status = ActionStatus.PENDING # Needs manual approval
303
+ else:
304
+ step.status = ActionStatus.APPROVED
305
+
306
+ # Plan is approved if all steps are approved
307
+ if all(s.status == ActionStatus.APPROVED for s in plan.steps):
308
+ plan.status = ActionStatus.APPROVED
309
+ return True
310
+
311
+ return False # Some steps need manual approval
312
+
313
+ else: # manual
314
+ # In manual mode, we return False to indicate approval is needed
315
+ return False
316
+
317
+ async def apply_plan(self, plan: ActionPlan, force: bool = False) -> ActionResult:
318
+ """
319
+ Apply an approved plan.
320
+
321
+ Args:
322
+ plan: The plan to apply
323
+ force: Force apply even if not fully approved
324
+
325
+ Returns:
326
+ ActionResult with application status
327
+ """
328
+ if self.runtime.read_only:
329
+ return ActionResult(
330
+ success=False,
331
+ plan=plan,
332
+ error="Runtime is in read-only mode",
333
+ read_only_blocked=True
334
+ )
335
+
336
+ if plan.status != ActionStatus.APPROVED and not force:
337
+ return ActionResult(
338
+ success=False,
339
+ plan=plan,
340
+ error="Plan is not approved"
341
+ )
342
+
343
+ applied = 0
344
+ failed = 0
345
+ diff_parts = []
346
+
347
+ # Track in ACP session if available (but always do manual application)
348
+ if self.runtime.acp_ready:
349
+ try:
350
+ auto_approve = self.runtime.config.approval_mode == "auto"
351
+ await self.runtime.acp_apply_plan(
352
+ plan.to_dict(),
353
+ auto_approve=auto_approve
354
+ )
355
+ except Exception as e:
356
+ logger.warning(f"ACP tracking failed: {e}")
357
+
358
+ # Manual application (always execute)
359
+ for step in plan.steps:
360
+ if step.status not in [ActionStatus.APPROVED, ActionStatus.PENDING]:
361
+ continue
362
+
363
+ try:
364
+ result = await self._apply_step(step)
365
+ if result:
366
+ step.status = ActionStatus.APPLIED
367
+ step.result = result
368
+ applied += 1
369
+ if isinstance(result, dict) and "diff" in result:
370
+ diff_parts.append(result["diff"])
371
+ else:
372
+ step.status = ActionStatus.FAILED
373
+ failed += 1
374
+ except Exception as e:
375
+ step.status = ActionStatus.FAILED
376
+ step.error = str(e)
377
+ failed += 1
378
+
379
+ plan.status = ActionStatus.APPLIED if failed == 0 else ActionStatus.FAILED
380
+
381
+ return ActionResult(
382
+ success=failed == 0,
383
+ plan=plan,
384
+ applied_steps=applied,
385
+ failed_steps=failed,
386
+ diff_summary="\n".join(diff_parts) if diff_parts else None
387
+ )
388
+
389
+ async def _apply_step(self, step: ActionStep) -> Optional[Dict[str, Any]]:
390
+ """Apply a single step."""
391
+ workspace = Path(self.runtime.config.workspace)
392
+
393
+ if step.action_type == ActionType.FILE_CREATE:
394
+ target = workspace / step.target
395
+ target.parent.mkdir(parents=True, exist_ok=True)
396
+ content = step.params.get("content", "")
397
+ target.write_text(content)
398
+ return {"created": str(target), "diff": f"+++ {target}\n+ (new file)"}
399
+
400
+ elif step.action_type == ActionType.FILE_EDIT:
401
+ target = workspace / step.target
402
+ if not target.exists():
403
+ return None
404
+
405
+ old_content = target.read_text()
406
+ new_content = step.params.get("new_content", old_content)
407
+ target.write_text(new_content)
408
+
409
+ return {
410
+ "edited": str(target),
411
+ "diff": f"--- {target}\n+++ {target}\n(content changed)"
412
+ }
413
+
414
+ elif step.action_type == ActionType.FILE_DELETE:
415
+ target = workspace / step.target
416
+ if target.exists():
417
+ target.unlink()
418
+ return {"deleted": str(target), "diff": f"--- {target}\n- (deleted)"}
419
+ return None
420
+
421
+ elif step.action_type == ActionType.FILE_RENAME:
422
+ source = workspace / step.params.get("source", "")
423
+ dest = workspace / step.params.get("dest", "")
424
+ if source.exists():
425
+ source.rename(dest)
426
+ return {"renamed": f"{source} -> {dest}"}
427
+ return None
428
+
429
+ elif step.action_type == ActionType.SHELL_COMMAND:
430
+ import subprocess
431
+ result = subprocess.run(
432
+ step.target,
433
+ shell=True,
434
+ capture_output=True,
435
+ text=True,
436
+ cwd=str(workspace),
437
+ timeout=30
438
+ )
439
+ return {
440
+ "command": step.target,
441
+ "stdout": result.stdout,
442
+ "stderr": result.stderr,
443
+ "returncode": result.returncode
444
+ }
445
+
446
+ return None
447
+
448
+ async def verify_plan(self, plan: ActionPlan) -> ActionResult:
449
+ """
450
+ Verify that a plan was applied correctly.
451
+
452
+ Args:
453
+ plan: The plan to verify
454
+
455
+ Returns:
456
+ ActionResult with verification status
457
+ """
458
+ if plan.status != ActionStatus.APPLIED:
459
+ return ActionResult(
460
+ success=False,
461
+ plan=plan,
462
+ error="Plan was not applied"
463
+ )
464
+
465
+ workspace = Path(self.runtime.config.workspace)
466
+ verified = 0
467
+ failed = 0
468
+
469
+ for step in plan.steps:
470
+ if step.status != ActionStatus.APPLIED:
471
+ continue
472
+
473
+ try:
474
+ if step.action_type == ActionType.FILE_CREATE:
475
+ target = workspace / step.target
476
+ if target.exists():
477
+ step.status = ActionStatus.VERIFIED
478
+ verified += 1
479
+ else:
480
+ step.status = ActionStatus.FAILED
481
+ step.error = "File was not created"
482
+ failed += 1
483
+
484
+ elif step.action_type == ActionType.FILE_DELETE:
485
+ target = workspace / step.target
486
+ if not target.exists():
487
+ step.status = ActionStatus.VERIFIED
488
+ verified += 1
489
+ else:
490
+ step.status = ActionStatus.FAILED
491
+ step.error = "File was not deleted"
492
+ failed += 1
493
+
494
+ else:
495
+ # For other types, assume verified if applied
496
+ step.status = ActionStatus.VERIFIED
497
+ verified += 1
498
+
499
+ except Exception as e:
500
+ step.status = ActionStatus.FAILED
501
+ step.error = str(e)
502
+ failed += 1
503
+
504
+ plan.status = ActionStatus.VERIFIED if failed == 0 else ActionStatus.FAILED
505
+
506
+ return ActionResult(
507
+ success=failed == 0,
508
+ plan=plan,
509
+ applied_steps=verified,
510
+ failed_steps=failed
511
+ )
512
+
513
+ async def execute(self, prompt: str, auto_approve: bool = False) -> ActionResult:
514
+ """
515
+ Execute a full plan → approve → apply → verify cycle.
516
+
517
+ Args:
518
+ prompt: The modification request
519
+ auto_approve: Whether to auto-approve
520
+
521
+ Returns:
522
+ ActionResult with final status
523
+ """
524
+ # Create plan
525
+ result = await self.create_plan(prompt)
526
+ if not result.success:
527
+ return result
528
+
529
+ plan = result.plan
530
+
531
+ # Approve
532
+ approved = await self.approve_plan(plan, auto=auto_approve)
533
+ if not approved and self.runtime.config.approval_mode == "manual":
534
+ return ActionResult(
535
+ success=False,
536
+ plan=plan,
537
+ error="Plan requires manual approval"
538
+ )
539
+
540
+ # Apply
541
+ result = await self.apply_plan(plan)
542
+ if not result.success:
543
+ return result
544
+
545
+ # Verify
546
+ return await self.verify_plan(plan)