acra-cli 0.1.1__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.
- acra/__init__.py +7 -0
- acra/__main__.py +4 -0
- acra/agents/__init__.py +0 -0
- acra/agents/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/__pycache__/llm.cpython-312.pyc +0 -0
- acra/agents/__pycache__/llm_utils.cpython-312.pyc +0 -0
- acra/agents/coder/__init__.py +0 -0
- acra/agents/coder/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/coder/__pycache__/coder_agent.cpython-312.pyc +0 -0
- acra/agents/coder/__pycache__/coder_chain.cpython-312.pyc +0 -0
- acra/agents/coder/coder_agent.py +235 -0
- acra/agents/coder/coder_chain.py +204 -0
- acra/agents/critic/__init__.py +0 -0
- acra/agents/critic/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/critic/__pycache__/critic_agent.cpython-312.pyc +0 -0
- acra/agents/critic/__pycache__/critic_chain.cpython-312.pyc +0 -0
- acra/agents/critic/critic_agent.py +199 -0
- acra/agents/critic/critic_chain.py +268 -0
- acra/agents/executor/__init__.py +0 -0
- acra/agents/executor/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/docker_runner.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/executor_agent.cpython-312.pyc +0 -0
- acra/agents/executor/__pycache__/sandbox_runner.cpython-312.pyc +0 -0
- acra/agents/executor/docker_runner.py +114 -0
- acra/agents/executor/executor_agent.py +142 -0
- acra/agents/executor/sandbox_runner.py +144 -0
- acra/agents/human/__init__.py +0 -0
- acra/agents/human/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/human/__pycache__/human_node.cpython-312.pyc +0 -0
- acra/agents/human/human_node.py +37 -0
- acra/agents/llm.py +263 -0
- acra/agents/llm_utils.py +161 -0
- acra/agents/memory/__init__.py +0 -0
- acra/agents/memory/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/memory_agent.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/memory_manager.cpython-312.pyc +0 -0
- acra/agents/memory/__pycache__/retrieval.cpython-312.pyc +0 -0
- acra/agents/memory/memory_agent.py +239 -0
- acra/agents/memory/memory_manager.py +273 -0
- acra/agents/memory/retrieval.py +355 -0
- acra/agents/planner/__init__.py +0 -0
- acra/agents/planner/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/planner/__pycache__/planner_agent.cpython-312.pyc +0 -0
- acra/agents/planner/__pycache__/planner_chain.cpython-312.pyc +0 -0
- acra/agents/planner/planner_agent.py +97 -0
- acra/agents/planner/planner_chain.py +160 -0
- acra/agents/researcher/__init__.py +1 -0
- acra/agents/researcher/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/agents/researcher/__pycache__/researcher_agent.cpython-312.pyc +0 -0
- acra/agents/researcher/__pycache__/researcher_chain.cpython-312.pyc +0 -0
- acra/agents/researcher/researcher_agent.py +231 -0
- acra/agents/researcher/researcher_chain.py +193 -0
- acra/brain/__init__.py +6 -0
- acra/brain/model_registry.py +19 -0
- acra/brain/provider_manager.py +21 -0
- acra/cli.py +52 -0
- acra/commands/__init__.py +29 -0
- acra/commands/ask.py +49 -0
- acra/commands/brain.py +58 -0
- acra/commands/config.py +25 -0
- acra/commands/context.py +30 -0
- acra/commands/graph.py +18 -0
- acra/commands/keys.py +40 -0
- acra/commands/logs.py +13 -0
- acra/commands/memory.py +27 -0
- acra/commands/plugin.py +18 -0
- acra/commands/research.py +65 -0
- acra/commands/serve.py +13 -0
- acra/commands/session.py +20 -0
- acra/commands/utils.py +12 -0
- acra/config/__init__.py +36 -0
- acra/config/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/config/__pycache__/defaults.cpython-312.pyc +0 -0
- acra/config/__pycache__/profile_manager.cpython-312.pyc +0 -0
- acra/config/defaults.py +88 -0
- acra/config/profile_manager.py +87 -0
- acra/execution/__init__.py +1 -0
- acra/execution/logs/__init__.py +1 -0
- acra/execution/logs/error_logs/__init__.py +1 -0
- acra/execution/logs/execution_logs/__init__.py +1 -0
- acra/execution/sandbox/__init__.py +1 -0
- acra/execution/sandbox/docker_executor.py +209 -0
- acra/execution/sandbox/isolated_runner.py +148 -0
- acra/execution/sandbox/sandbox_manager.py +178 -0
- acra/graph/__init__.py +0 -0
- acra/graph/checkpoint.py +85 -0
- acra/graph/conditional_edges.py +103 -0
- acra/graph/edges.py +85 -0
- acra/graph/graph_visualizer.py +175 -0
- acra/graph/nodes.py +68 -0
- acra/graph/router.py +69 -0
- acra/graph/state.py +93 -0
- acra/graph/workflow.py +117 -0
- acra/memory/__init__.py +1 -0
- acra/memory/checkpoints/__init__.py +1 -0
- acra/memory/checkpoints/data/__init__.py +1 -0
- acra/memory/checkpoints/postgres_checkpoint.py +247 -0
- acra/memory/checkpoints/sqlite_checkpoint.py +309 -0
- acra/memory/chroma_db/__init__.py +1 -0
- acra/memory/conversation_memory/__init__.py +1 -0
- acra/memory/conversation_memory/long_term.py +229 -0
- acra/memory/conversation_memory/short_term.py +158 -0
- acra/memory/storage/__init__.py +1 -0
- acra/memory/vector_store/__init__.py +0 -0
- acra/memory/vector_store/chroma_store.py +260 -0
- acra/memory/vector_store/embeddings.py +43 -0
- acra/observability/__init__.py +1 -0
- acra/observability/langsmith_tracing.py +91 -0
- acra/observability/metrics.py +189 -0
- acra/observability/monitoring.py +162 -0
- acra/observability/token_tracking.py +131 -0
- acra/prompts/__init__.py +1 -0
- acra/schemas/__init__.py +0 -0
- acra/schemas/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/coder_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/critic_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/execution_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/planner_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/researcher_schema.cpython-312.pyc +0 -0
- acra/schemas/__pycache__/shared_schema.cpython-312.pyc +0 -0
- acra/schemas/coder_schema.py +257 -0
- acra/schemas/critic_schema.py +177 -0
- acra/schemas/execution_schema.py +74 -0
- acra/schemas/planner_schema.py +129 -0
- acra/schemas/researcher_schema.py +220 -0
- acra/schemas/shared_schema.py +329 -0
- acra/tools/__init__.py +1 -0
- acra/tools/code_tools/__init__.py +1 -0
- acra/tools/code_tools/file_reader.py +156 -0
- acra/tools/code_tools/file_writer.py +108 -0
- acra/tools/code_tools/python_repl.py +72 -0
- acra/tools/code_tools/terminal_runner.py +98 -0
- acra/tools/github_tools/__init__.py +1 -0
- acra/tools/github_tools/commit_generator.py +91 -0
- acra/tools/github_tools/github_search.py +113 -0
- acra/tools/github_tools/repo_analyzer.py +138 -0
- acra/tools/integration_test_helper.py +370 -0
- acra/tools/validation_tools/__init__.py +1 -0
- acra/tools/validation_tools/dependency_checker.py +101 -0
- acra/tools/validation_tools/environment_config_validator.py +353 -0
- acra/tools/validation_tools/http_validator.py +336 -0
- acra/tools/validation_tools/response_validator.py +407 -0
- acra/tools/validation_tools/security_checker.py +93 -0
- acra/tools/validation_tools/syntax_checker.py +75 -0
- acra/tools/validation_tools/web_framework_validator.py +382 -0
- acra/tools/web_tools/__init__.py +1 -0
- acra/tools/web_tools/arxiv_search.py +110 -0
- acra/tools/web_tools/serpapi_search.py +106 -0
- acra/tools/web_tools/tavily_search.py +96 -0
- acra/ui/__init__.py +8 -0
- acra/ui/__pycache__/__init__.cpython-312.pyc +0 -0
- acra/ui/__pycache__/banner.cpython-312.pyc +0 -0
- acra/ui/__pycache__/components.cpython-312.pyc +0 -0
- acra/ui/__pycache__/shell.cpython-312.pyc +0 -0
- acra/ui/__pycache__/theme.cpython-312.pyc +0 -0
- acra/ui/banner.py +40 -0
- acra/ui/components.py +32 -0
- acra/ui/shell.py +91 -0
- acra/ui/theme.py +28 -0
- acra/utils/__init__.py +21 -0
- acra/utils/context_manager.py +20 -0
- acra/utils/keyring_manager.py +83 -0
- acra/utils/output_formatter.py +18 -0
- acra/utils/session_manager.py +59 -0
- acra_cli-0.1.1.dist-info/METADATA +616 -0
- acra_cli-0.1.1.dist-info/RECORD +169 -0
- acra_cli-0.1.1.dist-info/WHEEL +5 -0
- acra_cli-0.1.1.dist-info/licenses/LICENSE +661 -0
- acra_cli-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Profile management for acra."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from getpass import getpass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
CONFIG_DIR = Path.home() / ".acra"
|
|
10
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
11
|
+
DEFAULT_PROFILE_NAME = "default"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _ensure_config_dir() -> None:
|
|
15
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _safe_load_config() -> Dict[str, Any]:
|
|
19
|
+
_ensure_config_dir()
|
|
20
|
+
if not CONFIG_FILE.exists():
|
|
21
|
+
return {"profiles": {DEFAULT_PROFILE_NAME: {}}, "active": DEFAULT_PROFILE_NAME}
|
|
22
|
+
try:
|
|
23
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
24
|
+
return json.load(f)
|
|
25
|
+
except Exception:
|
|
26
|
+
return {"profiles": {DEFAULT_PROFILE_NAME: {}}, "active": DEFAULT_PROFILE_NAME}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _save_config(data: Dict[str, Any]) -> None:
|
|
30
|
+
_ensure_config_dir()
|
|
31
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
32
|
+
json.dump(data, f, indent=2)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ProfileManager:
|
|
36
|
+
"""Manage named acra configuration profiles."""
|
|
37
|
+
|
|
38
|
+
def __init__(self) -> None:
|
|
39
|
+
self._data = _safe_load_config()
|
|
40
|
+
|
|
41
|
+
def list_profiles(self) -> Dict[str, Any]:
|
|
42
|
+
return self._data.get("profiles", {})
|
|
43
|
+
|
|
44
|
+
def load_profile(self, profile_name: Optional[str] = None) -> Dict[str, Any]:
|
|
45
|
+
if profile_name is None:
|
|
46
|
+
profile_name = self._data.get("active", DEFAULT_PROFILE_NAME)
|
|
47
|
+
profiles = self._data.get("profiles", {})
|
|
48
|
+
return profiles.get(profile_name, profiles.get(DEFAULT_PROFILE_NAME, {}))
|
|
49
|
+
|
|
50
|
+
def save_profile(self, profile_name: str, profile_data: Dict[str, Any]) -> None:
|
|
51
|
+
profiles = self._data.setdefault("profiles", {})
|
|
52
|
+
profiles[profile_name] = profile_data
|
|
53
|
+
self._data["active"] = profile_name
|
|
54
|
+
_save_config(self._data)
|
|
55
|
+
|
|
56
|
+
def set_active(self, profile_name: str) -> None:
|
|
57
|
+
profiles = self._data.setdefault("profiles", {})
|
|
58
|
+
if profile_name in profiles:
|
|
59
|
+
self._data["active"] = profile_name
|
|
60
|
+
_save_config(self._data)
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError(f"Profile not found: {profile_name}")
|
|
63
|
+
|
|
64
|
+
def run_wizard(self, profile_name: Optional[str] = None) -> str:
|
|
65
|
+
profile_name = profile_name or DEFAULT_PROFILE_NAME
|
|
66
|
+
print("Welcome to acra setup wizard")
|
|
67
|
+
provider = input("Choose provider (gemini/openai/groq/ollama/huggingface_cloud/huggingface_local): ").strip() or "gemini"
|
|
68
|
+
model = input("Model name: ").strip() or "gemini-2.5-flash"
|
|
69
|
+
api_key = getpass("Provider API key (hidden): ")
|
|
70
|
+
theme = input("Theme (dark-orange/dark-blue/dark-green): ").strip() or "dark-orange"
|
|
71
|
+
workspace = input("Workspace path (default=current directory): ").strip() or os.getcwd()
|
|
72
|
+
research_keys = {}
|
|
73
|
+
print("Configure research keys. Press enter to skip a source.")
|
|
74
|
+
for source in ["web", "github", "docs", "arxiv"]:
|
|
75
|
+
prompt = f"Research {source} API key (if required): "
|
|
76
|
+
value = getpass(prompt) if source != "arxiv" else input(prompt).strip()
|
|
77
|
+
research_keys[source] = value or None
|
|
78
|
+
|
|
79
|
+
profile = {
|
|
80
|
+
"provider": provider,
|
|
81
|
+
"model": model,
|
|
82
|
+
"theme": theme,
|
|
83
|
+
"workspace": workspace,
|
|
84
|
+
"research_keys": research_keys,
|
|
85
|
+
}
|
|
86
|
+
self.save_profile(profile_name, profile)
|
|
87
|
+
return profile_name
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/execution
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/execution/logs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/execution/logs/error_logs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/execution/logs/execution_logs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package acra/execution/sandbox
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import shlex
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# docker executer
|
|
9
|
+
|
|
10
|
+
class DockerExecutor:
|
|
11
|
+
"""
|
|
12
|
+
Docker-based isolated code execution.
|
|
13
|
+
|
|
14
|
+
Responsibilities:
|
|
15
|
+
- execute code safely
|
|
16
|
+
- isolate runtime environment
|
|
17
|
+
- prevent host contamination
|
|
18
|
+
- support autonomous execution
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# run python file
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def run_python_file(
|
|
26
|
+
file_path: str,
|
|
27
|
+
working_directory: str,
|
|
28
|
+
timeout: int = 60
|
|
29
|
+
) -> Dict:
|
|
30
|
+
"""
|
|
31
|
+
Execute Python file inside Docker.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
|
|
36
|
+
command = [
|
|
37
|
+
|
|
38
|
+
"docker",
|
|
39
|
+
"run",
|
|
40
|
+
|
|
41
|
+
"--rm",
|
|
42
|
+
"--network",
|
|
43
|
+
"none",
|
|
44
|
+
"--user",
|
|
45
|
+
f"{os.getuid()}:{os.getgid()}",
|
|
46
|
+
"--memory",
|
|
47
|
+
"256m",
|
|
48
|
+
"--cpus",
|
|
49
|
+
"1",
|
|
50
|
+
"--pids-limit",
|
|
51
|
+
"128",
|
|
52
|
+
"--cap-drop",
|
|
53
|
+
"ALL",
|
|
54
|
+
"--security-opt",
|
|
55
|
+
"no-new-privileges",
|
|
56
|
+
|
|
57
|
+
"-v",
|
|
58
|
+
f"{Path(working_directory).resolve()}:/app:rw",
|
|
59
|
+
|
|
60
|
+
"-w",
|
|
61
|
+
"/app",
|
|
62
|
+
|
|
63
|
+
"python:3.11-slim",
|
|
64
|
+
|
|
65
|
+
"python",
|
|
66
|
+
file_path
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
|
|
71
|
+
command,
|
|
72
|
+
|
|
73
|
+
capture_output=True,
|
|
74
|
+
|
|
75
|
+
text=True,
|
|
76
|
+
|
|
77
|
+
timeout=timeout
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
success = (
|
|
81
|
+
result.returncode == 0
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
|
|
86
|
+
"success": success,
|
|
87
|
+
|
|
88
|
+
"stdout": result.stdout,
|
|
89
|
+
|
|
90
|
+
"stderr": result.stderr,
|
|
91
|
+
|
|
92
|
+
"return_code": (
|
|
93
|
+
result.returncode
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
except subprocess.TimeoutExpired:
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
|
|
101
|
+
"success": False,
|
|
102
|
+
|
|
103
|
+
"stdout": "",
|
|
104
|
+
|
|
105
|
+
"stderr": (
|
|
106
|
+
"Execution timed out."
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
|
|
114
|
+
"success": False,
|
|
115
|
+
|
|
116
|
+
"stdout": "",
|
|
117
|
+
|
|
118
|
+
"stderr": str(e)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# run shell command
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def run_command(
|
|
126
|
+
command: Union[str, List[str]],
|
|
127
|
+
working_directory: str,
|
|
128
|
+
timeout: int = 60
|
|
129
|
+
) -> Dict:
|
|
130
|
+
"""
|
|
131
|
+
Execute shell command in Docker.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
command_args = shlex.split(command) if isinstance(command, str) else command
|
|
136
|
+
|
|
137
|
+
docker_command = [
|
|
138
|
+
|
|
139
|
+
"docker",
|
|
140
|
+
"run",
|
|
141
|
+
|
|
142
|
+
"--rm",
|
|
143
|
+
"--network",
|
|
144
|
+
"none",
|
|
145
|
+
"--user",
|
|
146
|
+
f"{os.getuid()}:{os.getgid()}",
|
|
147
|
+
"--memory",
|
|
148
|
+
"256m",
|
|
149
|
+
"--cpus",
|
|
150
|
+
"1",
|
|
151
|
+
"--pids-limit",
|
|
152
|
+
"128",
|
|
153
|
+
"--cap-drop",
|
|
154
|
+
"ALL",
|
|
155
|
+
"--security-opt",
|
|
156
|
+
"no-new-privileges",
|
|
157
|
+
|
|
158
|
+
"-v",
|
|
159
|
+
f"{Path(working_directory).resolve()}:/app:rw",
|
|
160
|
+
|
|
161
|
+
"-w",
|
|
162
|
+
"/app",
|
|
163
|
+
|
|
164
|
+
"python:3.11-slim",
|
|
165
|
+
|
|
166
|
+
*command_args
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
|
|
171
|
+
docker_command,
|
|
172
|
+
|
|
173
|
+
capture_output=True,
|
|
174
|
+
|
|
175
|
+
text=True,
|
|
176
|
+
|
|
177
|
+
timeout=timeout
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
|
|
182
|
+
"success": (
|
|
183
|
+
result.returncode == 0
|
|
184
|
+
),
|
|
185
|
+
|
|
186
|
+
"stdout": result.stdout,
|
|
187
|
+
|
|
188
|
+
"stderr": result.stderr,
|
|
189
|
+
|
|
190
|
+
"return_code": (
|
|
191
|
+
result.returncode
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
|
|
199
|
+
"success": False,
|
|
200
|
+
|
|
201
|
+
"stdout": "",
|
|
202
|
+
|
|
203
|
+
"stderr": str(e)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# global executer
|
|
208
|
+
|
|
209
|
+
docker_executor = DockerExecutor()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# isolated runner
|
|
6
|
+
|
|
7
|
+
class IsolatedRunner:
|
|
8
|
+
"""
|
|
9
|
+
Lightweight isolated execution runner.
|
|
10
|
+
|
|
11
|
+
Responsibilities:
|
|
12
|
+
- local isolated execution
|
|
13
|
+
- rapid debugging workflows
|
|
14
|
+
- development-time execution
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
timeout: int = 30
|
|
20
|
+
):
|
|
21
|
+
|
|
22
|
+
self.timeout = timeout
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# run python script
|
|
26
|
+
|
|
27
|
+
def run_python_script(
|
|
28
|
+
self,
|
|
29
|
+
script_path: str,
|
|
30
|
+
cwd: Optional[str] = None
|
|
31
|
+
) -> Dict:
|
|
32
|
+
"""
|
|
33
|
+
Execute local Python script.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
|
|
38
|
+
result = subprocess.run(
|
|
39
|
+
|
|
40
|
+
["python", script_path],
|
|
41
|
+
|
|
42
|
+
capture_output=True,
|
|
43
|
+
|
|
44
|
+
text=True,
|
|
45
|
+
|
|
46
|
+
cwd=cwd,
|
|
47
|
+
|
|
48
|
+
timeout=self.timeout
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
|
|
53
|
+
"success": (
|
|
54
|
+
result.returncode == 0
|
|
55
|
+
),
|
|
56
|
+
|
|
57
|
+
"stdout": result.stdout,
|
|
58
|
+
|
|
59
|
+
"stderr": result.stderr,
|
|
60
|
+
|
|
61
|
+
"return_code": (
|
|
62
|
+
result.returncode
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
except subprocess.TimeoutExpired:
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
|
|
70
|
+
"success": False,
|
|
71
|
+
|
|
72
|
+
"stdout": "",
|
|
73
|
+
|
|
74
|
+
"stderr": (
|
|
75
|
+
"Execution timed out."
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
|
|
83
|
+
"success": False,
|
|
84
|
+
|
|
85
|
+
"stdout": "",
|
|
86
|
+
|
|
87
|
+
"stderr": str(e)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# run terminal command
|
|
92
|
+
|
|
93
|
+
def run_command(
|
|
94
|
+
self,
|
|
95
|
+
command: str,
|
|
96
|
+
cwd: Optional[str] = None
|
|
97
|
+
) -> Dict:
|
|
98
|
+
"""
|
|
99
|
+
Execute shell command locally.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
|
|
106
|
+
command,
|
|
107
|
+
|
|
108
|
+
shell=True,
|
|
109
|
+
|
|
110
|
+
capture_output=True,
|
|
111
|
+
|
|
112
|
+
text=True,
|
|
113
|
+
|
|
114
|
+
cwd=cwd,
|
|
115
|
+
|
|
116
|
+
timeout=self.timeout
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
|
|
121
|
+
"success": (
|
|
122
|
+
result.returncode == 0
|
|
123
|
+
),
|
|
124
|
+
|
|
125
|
+
"stdout": result.stdout,
|
|
126
|
+
|
|
127
|
+
"stderr": result.stderr,
|
|
128
|
+
|
|
129
|
+
"return_code": (
|
|
130
|
+
result.returncode
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
|
|
138
|
+
"success": False,
|
|
139
|
+
|
|
140
|
+
"stdout": "",
|
|
141
|
+
|
|
142
|
+
"stderr": str(e)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# global runner
|
|
147
|
+
|
|
148
|
+
isolated_runner = IsolatedRunner()
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uuid
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# sandbox config
|
|
8
|
+
|
|
9
|
+
SANDBOX_ROOT = "execution/generated_projects"
|
|
10
|
+
|
|
11
|
+
os.makedirs(SANDBOX_ROOT, exist_ok=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# sandbox manager
|
|
15
|
+
|
|
16
|
+
class SandboxManager:
|
|
17
|
+
"""
|
|
18
|
+
Sandbox environment manager.
|
|
19
|
+
|
|
20
|
+
Responsibilities:
|
|
21
|
+
- create isolated workspaces
|
|
22
|
+
- manage execution directories
|
|
23
|
+
- cleanup temporary projects
|
|
24
|
+
- support autonomous execution
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
|
|
29
|
+
self.sandbox_root = SANDBOX_ROOT
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# create sandbox
|
|
33
|
+
|
|
34
|
+
def create_sandbox(
|
|
35
|
+
self,
|
|
36
|
+
prefix: str = "workflow"
|
|
37
|
+
) -> Dict:
|
|
38
|
+
"""
|
|
39
|
+
Create isolated execution workspace.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
|
|
44
|
+
sandbox_id = (
|
|
45
|
+
f"{prefix}_{uuid.uuid4().hex[:8]}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
sandbox_path = os.path.join(
|
|
49
|
+
self.sandbox_root,
|
|
50
|
+
sandbox_id
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
Path(sandbox_path).mkdir(
|
|
54
|
+
parents=True,
|
|
55
|
+
exist_ok=True
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
|
|
60
|
+
"success": True,
|
|
61
|
+
|
|
62
|
+
"sandbox_id": sandbox_id,
|
|
63
|
+
|
|
64
|
+
"sandbox_path": sandbox_path
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
|
|
71
|
+
"success": False,
|
|
72
|
+
|
|
73
|
+
"error": str(e)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# delete sandbox
|
|
78
|
+
|
|
79
|
+
def delete_sandbox(
|
|
80
|
+
self,
|
|
81
|
+
sandbox_path: str
|
|
82
|
+
) -> Dict:
|
|
83
|
+
"""
|
|
84
|
+
Remove sandbox workspace.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
|
|
89
|
+
if os.path.exists(
|
|
90
|
+
sandbox_path
|
|
91
|
+
):
|
|
92
|
+
|
|
93
|
+
for root, dirs, files in os.walk(
|
|
94
|
+
sandbox_path,
|
|
95
|
+
topdown=False
|
|
96
|
+
):
|
|
97
|
+
|
|
98
|
+
for file in files:
|
|
99
|
+
|
|
100
|
+
os.remove(
|
|
101
|
+
os.path.join(
|
|
102
|
+
root,
|
|
103
|
+
file
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for directory in dirs:
|
|
108
|
+
|
|
109
|
+
os.rmdir(
|
|
110
|
+
os.path.join(
|
|
111
|
+
root,
|
|
112
|
+
directory
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
os.rmdir(sandbox_path)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
|
|
120
|
+
"success": True,
|
|
121
|
+
|
|
122
|
+
"sandbox_path": sandbox_path
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
|
|
129
|
+
"success": False,
|
|
130
|
+
|
|
131
|
+
"error": str(e)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# list sandboxes
|
|
136
|
+
|
|
137
|
+
def list_sandboxes(self) -> Dict:
|
|
138
|
+
"""
|
|
139
|
+
Retrieve active sandboxes.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
|
|
144
|
+
sandboxes = []
|
|
145
|
+
|
|
146
|
+
for item in os.listdir(
|
|
147
|
+
self.sandbox_root
|
|
148
|
+
):
|
|
149
|
+
|
|
150
|
+
full_path = os.path.join(
|
|
151
|
+
self.sandbox_root,
|
|
152
|
+
item
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if os.path.isdir(full_path):
|
|
156
|
+
|
|
157
|
+
sandboxes.append(full_path)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
|
|
161
|
+
"success": True,
|
|
162
|
+
|
|
163
|
+
"sandboxes": sandboxes
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
|
|
170
|
+
"success": False,
|
|
171
|
+
|
|
172
|
+
"error": str(e)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# global sandbox manager
|
|
177
|
+
|
|
178
|
+
sandbox_manager = SandboxManager()
|
acra/graph/__init__.py
ADDED
|
File without changes
|