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,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)
|