ara-cli 0.1.9.69__py3-none-any.whl → 0.1.10.8__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.
Potentially problematic release.
This version of ara-cli might be problematic. Click here for more details.
- ara_cli/__init__.py +18 -2
- ara_cli/__main__.py +248 -62
- ara_cli/ara_command_action.py +155 -86
- ara_cli/ara_config.py +226 -80
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch_templates.py +14 -0
- ara_cli/ara_subcommands/list.py +65 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +649 -68
- ara_cli/artefact_creator.py +8 -11
- ara_cli/artefact_deleter.py +2 -4
- ara_cli/artefact_fuzzy_search.py +22 -10
- ara_cli/artefact_link_updater.py +4 -4
- ara_cli/artefact_lister.py +29 -55
- ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
- ara_cli/artefact_models/artefact_load.py +11 -3
- ara_cli/artefact_models/artefact_model.py +146 -39
- ara_cli/artefact_models/artefact_templates.py +70 -44
- ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
- ara_cli/artefact_models/epic_artefact_model.py +34 -26
- ara_cli/artefact_models/feature_artefact_model.py +203 -64
- ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
- ara_cli/artefact_models/serialize_helper.py +1 -1
- ara_cli/artefact_models/task_artefact_model.py +83 -15
- ara_cli/artefact_models/userstory_artefact_model.py +37 -27
- ara_cli/artefact_models/vision_artefact_model.py +23 -42
- ara_cli/artefact_reader.py +92 -91
- ara_cli/artefact_renamer.py +8 -4
- ara_cli/artefact_scan.py +66 -3
- ara_cli/chat.py +622 -162
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_communicator.py +62 -0
- ara_cli/chat_agent/agent_process_manager.py +211 -0
- ara_cli/chat_agent/agent_status_manager.py +73 -0
- ara_cli/chat_agent/agent_workspace_manager.py +76 -0
- ara_cli/commands/__init__.py +0 -0
- ara_cli/commands/command.py +7 -0
- ara_cli/commands/extract_command.py +15 -0
- ara_cli/commands/load_command.py +65 -0
- ara_cli/commands/load_image_command.py +34 -0
- ara_cli/commands/read_command.py +117 -0
- ara_cli/completers.py +144 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/error_handler.py +134 -0
- ara_cli/file_classifier.py +6 -5
- ara_cli/file_lister.py +1 -1
- ara_cli/file_loaders/__init__.py +0 -0
- ara_cli/file_loaders/binary_file_loader.py +33 -0
- ara_cli/file_loaders/document_file_loader.py +34 -0
- ara_cli/file_loaders/document_reader.py +245 -0
- ara_cli/file_loaders/document_readers.py +233 -0
- ara_cli/file_loaders/file_loader.py +50 -0
- ara_cli/file_loaders/file_loaders.py +123 -0
- ara_cli/file_loaders/image_processor.py +89 -0
- ara_cli/file_loaders/markdown_reader.py +75 -0
- ara_cli/file_loaders/text_file_loader.py +187 -0
- ara_cli/global_file_lister.py +51 -0
- ara_cli/list_filter.py +1 -1
- ara_cli/output_suppressor.py +1 -1
- ara_cli/prompt_extractor.py +215 -88
- ara_cli/prompt_handler.py +521 -134
- ara_cli/prompt_rag.py +2 -2
- ara_cli/tag_extractor.py +83 -38
- ara_cli/template_loader.py +245 -0
- ara_cli/template_manager.py +18 -13
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/update_config_prompt.py +9 -3
- ara_cli/version.py +1 -1
- ara_cli-0.1.10.8.dist-info/METADATA +241 -0
- ara_cli-0.1.10.8.dist-info/RECORD +193 -0
- tests/test_ara_command_action.py +73 -59
- tests/test_ara_config.py +341 -36
- tests/test_artefact_autofix.py +1060 -0
- tests/test_artefact_link_updater.py +3 -3
- tests/test_artefact_lister.py +52 -132
- tests/test_artefact_renamer.py +2 -2
- tests/test_artefact_scan.py +327 -33
- tests/test_chat.py +2063 -498
- tests/test_file_classifier.py +24 -1
- tests/test_file_creator.py +3 -5
- tests/test_file_lister.py +1 -1
- tests/test_global_file_lister.py +131 -0
- tests/test_list_filter.py +2 -2
- tests/test_prompt_handler.py +746 -0
- tests/test_tag_extractor.py +19 -13
- tests/test_template_loader.py +192 -0
- tests/test_template_manager.py +5 -4
- tests/test_update_config_prompt.py +2 -2
- ara_cli/ara_command_parser.py +0 -327
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- ara_cli/templates/template.businessgoal +0 -10
- ara_cli/templates/template.capability +0 -10
- ara_cli/templates/template.epic +0 -15
- ara_cli/templates/template.example +0 -6
- ara_cli/templates/template.feature +0 -26
- ara_cli/templates/template.issue +0 -14
- ara_cli/templates/template.keyfeature +0 -15
- ara_cli/templates/template.task +0 -6
- ara_cli/templates/template.userstory +0 -17
- ara_cli/templates/template.vision +0 -14
- ara_cli-0.1.9.69.dist-info/METADATA +0 -16
- ara_cli-0.1.9.69.dist-info/RECORD +0 -158
- tests/test_ara_autofix.py +0 -219
- {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import queue
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AgentCommunicator:
|
|
6
|
+
def __init__(self, agent_process_manager):
|
|
7
|
+
self.agent_process_manager = agent_process_manager
|
|
8
|
+
self.agent_process = agent_process_manager.agent_process
|
|
9
|
+
self.agent_output_queue = agent_process_manager.agent_output_queue
|
|
10
|
+
self.chat_instance = agent_process_manager.chat_instance
|
|
11
|
+
self.status_manager = agent_process_manager.status_manager
|
|
12
|
+
|
|
13
|
+
def read_agent_output(self):
|
|
14
|
+
try:
|
|
15
|
+
for line in iter(self.agent_process.stdout.readline, ""):
|
|
16
|
+
if not line:
|
|
17
|
+
break
|
|
18
|
+
self.agent_output_queue.put(line)
|
|
19
|
+
except Exception as e:
|
|
20
|
+
self.agent_output_queue.put(f"Error reading agent output: {e}\n")
|
|
21
|
+
|
|
22
|
+
def _handle_agent_answer(self, lines, answer_index):
|
|
23
|
+
for j in range(answer_index + 1, len(lines) - 1):
|
|
24
|
+
print(lines[j], flush=True)
|
|
25
|
+
print(flush=True)
|
|
26
|
+
|
|
27
|
+
self.agent_process_manager.agent_mode = True
|
|
28
|
+
self.chat_instance.prompt = "ara-agent> "
|
|
29
|
+
self.status_manager.update_status_file(mode="waiting for input")
|
|
30
|
+
|
|
31
|
+
def _process_lines(self, lines):
|
|
32
|
+
for i, line_text in enumerate(lines[:-1]):
|
|
33
|
+
print(line_text, flush=True)
|
|
34
|
+
if re.match(r'^\s*Answer:', line_text):
|
|
35
|
+
self._handle_agent_answer(lines, i)
|
|
36
|
+
return True, lines[-1]
|
|
37
|
+
return False, lines[-1]
|
|
38
|
+
|
|
39
|
+
def process_agent_output(self):
|
|
40
|
+
buffer = ""
|
|
41
|
+
while True:
|
|
42
|
+
try:
|
|
43
|
+
line = self.agent_output_queue.get(timeout=0.1)
|
|
44
|
+
buffer += line
|
|
45
|
+
|
|
46
|
+
if "\n" in buffer:
|
|
47
|
+
lines = buffer.split("\n")
|
|
48
|
+
found_answer, buffer = self._process_lines(lines)
|
|
49
|
+
if found_answer:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
except queue.Empty:
|
|
53
|
+
if self.agent_process and self.agent_process.poll() is not None:
|
|
54
|
+
if buffer:
|
|
55
|
+
print(buffer, flush=True)
|
|
56
|
+
self.agent_process_manager.cleanup_agent_process()
|
|
57
|
+
return
|
|
58
|
+
continue
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"Error processing agent output: {e}", flush=True)
|
|
61
|
+
self.agent_process_manager.cleanup_agent_process()
|
|
62
|
+
return
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from ara_cli.chat_agent.agent_workspace_manager import AgentWorkspaceManager
|
|
2
|
+
from ara_cli.error_handler import AraError, ErrorLevel
|
|
3
|
+
from ara_cli.chat_agent.agent_status_manager import AgentStatusManager
|
|
4
|
+
from ara_cli.chat_agent.agent_communicator import AgentCommunicator
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
8
|
+
import psutil
|
|
9
|
+
import queue
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentProcessManager:
|
|
13
|
+
_instance = None
|
|
14
|
+
|
|
15
|
+
def __new__(cls, chat_instance=None):
|
|
16
|
+
if cls._instance is None:
|
|
17
|
+
cls._instance = super(AgentProcessManager, cls).__new__(cls)
|
|
18
|
+
cls._instance._initialized = False
|
|
19
|
+
return cls._instance
|
|
20
|
+
|
|
21
|
+
def __init__(self, chat_instance=None):
|
|
22
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
23
|
+
if chat_instance:
|
|
24
|
+
self.chat_instance = chat_instance
|
|
25
|
+
return
|
|
26
|
+
if chat_instance is None:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"chat_instance must be provided for the first instantiation"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self.chat_instance = chat_instance
|
|
32
|
+
self.agent_process = None
|
|
33
|
+
self.agent_reader_thread = None
|
|
34
|
+
self.agent_output_queue = queue.Queue()
|
|
35
|
+
self.agent_mode = False
|
|
36
|
+
self.agent_name = None
|
|
37
|
+
self.status_manager = AgentStatusManager()
|
|
38
|
+
self._initialized = True
|
|
39
|
+
|
|
40
|
+
def cleanup_agent_process(self):
|
|
41
|
+
pid = self.status_manager.get_agent_pid()
|
|
42
|
+
if pid and psutil.pid_exists(pid):
|
|
43
|
+
try:
|
|
44
|
+
p = psutil.Process(pid)
|
|
45
|
+
p.terminate()
|
|
46
|
+
p.wait(timeout=3)
|
|
47
|
+
except (psutil.NoSuchProcess, psutil.TimeoutExpired):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
self.status_manager.clear_status()
|
|
51
|
+
|
|
52
|
+
if self.agent_process:
|
|
53
|
+
try:
|
|
54
|
+
self.agent_process.terminate()
|
|
55
|
+
except:
|
|
56
|
+
pass
|
|
57
|
+
self.agent_process = None
|
|
58
|
+
|
|
59
|
+
if self.agent_reader_thread and self.agent_reader_thread.is_alive():
|
|
60
|
+
self.agent_reader_thread = None
|
|
61
|
+
|
|
62
|
+
self.agent_mode = False
|
|
63
|
+
self.agent_name = None
|
|
64
|
+
if hasattr(self, "chat_instance"):
|
|
65
|
+
self.chat_instance.prompt = "ara> "
|
|
66
|
+
|
|
67
|
+
def start_agent(
|
|
68
|
+
self, agent_name, initial_prompt, artefact_classifier=None, artefact_name=None
|
|
69
|
+
):
|
|
70
|
+
if self.get_agent_status() != "No agent is currently running.":
|
|
71
|
+
raise AraError(
|
|
72
|
+
"An agent is already running. Use AGENT_STOP to stop it first."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if agent_name not in self.chat_instance.AVAILABLE_AGENTS:
|
|
76
|
+
raise AraError(f"Unknown agent: {agent_name}")
|
|
77
|
+
|
|
78
|
+
base_work_dir = AgentWorkspaceManager.determine_base_work_dir(
|
|
79
|
+
self.chat_instance
|
|
80
|
+
)
|
|
81
|
+
agent_workspace_dir = AgentWorkspaceManager.determine_agent_workspace(
|
|
82
|
+
self.chat_instance, artefact_classifier, artefact_name
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
cmd = [
|
|
86
|
+
"ara-agents",
|
|
87
|
+
agent_name,
|
|
88
|
+
"-u",
|
|
89
|
+
initial_prompt,
|
|
90
|
+
"-g",
|
|
91
|
+
"roundrobin",
|
|
92
|
+
"-b",
|
|
93
|
+
".",
|
|
94
|
+
"-s",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if artefact_classifier and artefact_name:
|
|
98
|
+
try:
|
|
99
|
+
artefact_path = self._find_artefact_path(
|
|
100
|
+
artefact_classifier, artefact_name
|
|
101
|
+
)
|
|
102
|
+
cmd.extend(["-r", artefact_path])
|
|
103
|
+
print(f"Starting {agent_name} with artefact: {artefact_path}")
|
|
104
|
+
print(f"Base work directory: {base_work_dir}")
|
|
105
|
+
print(f"Agent logs directory: {agent_workspace_dir}")
|
|
106
|
+
except AraError as e:
|
|
107
|
+
raise AraError(f"Error: {e}")
|
|
108
|
+
else:
|
|
109
|
+
print(f"Starting {agent_name}...")
|
|
110
|
+
print(f"Base work directory: {base_work_dir}")
|
|
111
|
+
print(f"Agent logs directory: {agent_workspace_dir}")
|
|
112
|
+
|
|
113
|
+
env = os.environ.copy()
|
|
114
|
+
env["CENTRAL_LOG_PATH"] = os.path.join(
|
|
115
|
+
agent_workspace_dir, "io_context.log")
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
self.agent_process = subprocess.Popen(
|
|
119
|
+
cmd,
|
|
120
|
+
stdin=subprocess.PIPE,
|
|
121
|
+
stdout=subprocess.PIPE,
|
|
122
|
+
stderr=subprocess.STDOUT,
|
|
123
|
+
text=True,
|
|
124
|
+
bufsize=1,
|
|
125
|
+
cwd=base_work_dir,
|
|
126
|
+
env=env,
|
|
127
|
+
)
|
|
128
|
+
self.agent_name = agent_name
|
|
129
|
+
self.status_manager.write_status(
|
|
130
|
+
self.agent_process.pid, self.agent_name, "processing"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
communicator = AgentCommunicator(self)
|
|
134
|
+
self.agent_reader_thread = threading.Thread(
|
|
135
|
+
target=communicator.read_agent_output, daemon=True
|
|
136
|
+
)
|
|
137
|
+
self.agent_reader_thread.start()
|
|
138
|
+
|
|
139
|
+
print(f"Agent {agent_name} started. Waiting for response...\n")
|
|
140
|
+
communicator.process_agent_output()
|
|
141
|
+
|
|
142
|
+
except FileNotFoundError:
|
|
143
|
+
raise AraError(
|
|
144
|
+
"Agent could not started."
|
|
145
|
+
"\nReason: 'ara-agents' command not found. Make sure ara-agents is locally installed.",
|
|
146
|
+
level=ErrorLevel.CRITICAL,
|
|
147
|
+
)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
self.cleanup_agent_process()
|
|
150
|
+
raise AraError(f"Error starting agent: {e}")
|
|
151
|
+
|
|
152
|
+
def _find_artefact_path(self, artefact_classifier, artefact_name):
|
|
153
|
+
from ara_cli.classifier import Classifier
|
|
154
|
+
|
|
155
|
+
classifier_dir = Classifier.get_sub_directory(artefact_classifier)
|
|
156
|
+
if not classifier_dir:
|
|
157
|
+
raise AraError(f"Unknown classifier: {artefact_classifier}")
|
|
158
|
+
|
|
159
|
+
chat_dir = os.path.dirname(self.chat_instance.chat_name)
|
|
160
|
+
ara_dir = os.path.join(chat_dir, "ara", classifier_dir)
|
|
161
|
+
|
|
162
|
+
for ext in [artefact_classifier, "md"]:
|
|
163
|
+
artefact_path = os.path.join(ara_dir, f"{artefact_name}.{ext}")
|
|
164
|
+
if os.path.exists(artefact_path):
|
|
165
|
+
return artefact_path
|
|
166
|
+
|
|
167
|
+
raise AraError(
|
|
168
|
+
f"Artefact not found: {artefact_name}.{artefact_classifier}")
|
|
169
|
+
|
|
170
|
+
def send_to_agent(self, text):
|
|
171
|
+
if not self.agent_process or self.agent_process.poll() is not None:
|
|
172
|
+
print("Error: Agent process is not running")
|
|
173
|
+
self.cleanup_agent_process()
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
self.agent_process.stdin.write(text + "\n")
|
|
178
|
+
self.agent_process.stdin.flush()
|
|
179
|
+
|
|
180
|
+
self.agent_mode = False
|
|
181
|
+
self.chat_instance.prompt = "ara> "
|
|
182
|
+
self.status_manager.update_status_file(mode="processing")
|
|
183
|
+
|
|
184
|
+
communicator = AgentCommunicator(self)
|
|
185
|
+
communicator.process_agent_output()
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
print(f"Error sending to agent: {e}")
|
|
189
|
+
self.cleanup_agent_process()
|
|
190
|
+
|
|
191
|
+
def continue_agent(self):
|
|
192
|
+
status = self.get_agent_status()
|
|
193
|
+
if "No agent" in status:
|
|
194
|
+
print("No agent is currently running.")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
if not self.agent_process or self.agent_process.poll() is not None:
|
|
198
|
+
print(
|
|
199
|
+
"AGENT_CONTINUE can only be used from the chat interface that started the agent."
|
|
200
|
+
)
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
if not self.agent_mode:
|
|
204
|
+
print("Agent is not waiting for input. Wait for 'ara-agent>' prompt.")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
print("Continuing agent with empty input...")
|
|
208
|
+
self.send_to_agent("")
|
|
209
|
+
|
|
210
|
+
def get_agent_status(self):
|
|
211
|
+
return self.status_manager.get_agent_status()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import psutil
|
|
4
|
+
from ara_cli.directory_navigator import DirectoryNavigator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AgentStatusManager:
|
|
8
|
+
def get_status_file_path(self):
|
|
9
|
+
ara_root = DirectoryNavigator.find_ara_directory_root()
|
|
10
|
+
if not ara_root:
|
|
11
|
+
return None
|
|
12
|
+
return os.path.join(ara_root, ".araconfig", "agent_status.json")
|
|
13
|
+
|
|
14
|
+
def get_agent_pid(self):
|
|
15
|
+
status_file = self.get_status_file_path()
|
|
16
|
+
if not status_file or not os.path.exists(status_file):
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
with open(status_file, "r") as f:
|
|
20
|
+
try:
|
|
21
|
+
status = json.load(f)
|
|
22
|
+
return status.get("pid")
|
|
23
|
+
except json.JSONDecodeError:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def update_status_file(self, mode):
|
|
27
|
+
status_file = self.get_status_file_path()
|
|
28
|
+
if not status_file or not os.path.exists(status_file):
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
with open(status_file, "r") as f:
|
|
33
|
+
status = json.load(f)
|
|
34
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
status["mode"] = mode
|
|
38
|
+
|
|
39
|
+
with open(status_file, "w") as f:
|
|
40
|
+
json.dump(status, f)
|
|
41
|
+
|
|
42
|
+
def get_agent_status(self):
|
|
43
|
+
status_file = self.get_status_file_path()
|
|
44
|
+
if not status_file or not os.path.exists(status_file):
|
|
45
|
+
return "No agent is currently running."
|
|
46
|
+
|
|
47
|
+
with open(status_file, "r") as f:
|
|
48
|
+
try:
|
|
49
|
+
status = json.load(f)
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
return "No agent is currently running."
|
|
52
|
+
|
|
53
|
+
pid = status.get("pid")
|
|
54
|
+
if not pid or not psutil.pid_exists(pid):
|
|
55
|
+
if os.path.exists(status_file):
|
|
56
|
+
os.remove(status_file)
|
|
57
|
+
return "No agent is currently running."
|
|
58
|
+
|
|
59
|
+
name = status.get("name")
|
|
60
|
+
mode = status.get("mode")
|
|
61
|
+
return f"Agent: {name} (running - {mode})"
|
|
62
|
+
|
|
63
|
+
def write_status(self, pid, name, mode):
|
|
64
|
+
status = {"pid": pid, "name": name, "mode": mode}
|
|
65
|
+
status_file = self.get_status_file_path()
|
|
66
|
+
if status_file:
|
|
67
|
+
with open(status_file, "w") as f:
|
|
68
|
+
json.dump(status, f)
|
|
69
|
+
|
|
70
|
+
def clear_status(self):
|
|
71
|
+
status_file = self.get_status_file_path()
|
|
72
|
+
if status_file and os.path.exists(status_file):
|
|
73
|
+
os.remove(status_file)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from ara_cli.error_handler import AraError
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentWorkspaceManager:
|
|
7
|
+
"""Manages workspace directories for agent execution."""
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def determine_agent_workspace(chat_instance, artefact_classifier=None, artefact_name=None):
|
|
11
|
+
"""
|
|
12
|
+
Determines the appropriate workspace directory for agent logs and data.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
chat_instance: The Chat instance
|
|
16
|
+
artefact_classifier: Optional artefact classifier (e.g., 'feature')
|
|
17
|
+
artefact_name: Optional artefact name
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
str: Absolute path to the agent workspace directory (for logs)
|
|
21
|
+
"""
|
|
22
|
+
base_directory = os.path.dirname(chat_instance.chat_name)
|
|
23
|
+
|
|
24
|
+
if artefact_classifier and artefact_name:
|
|
25
|
+
# Use artefact-specific workspace for logs
|
|
26
|
+
from ara_cli.classifier import Classifier
|
|
27
|
+
classifier_dir = Classifier.get_sub_directory(artefact_classifier)
|
|
28
|
+
if not classifier_dir:
|
|
29
|
+
raise AraError(f"Unknown classifier: {artefact_classifier}")
|
|
30
|
+
|
|
31
|
+
workspace_dir = os.path.join(
|
|
32
|
+
base_directory,
|
|
33
|
+
"ara",
|
|
34
|
+
classifier_dir,
|
|
35
|
+
f"{artefact_name}.data"
|
|
36
|
+
)
|
|
37
|
+
else:
|
|
38
|
+
# Use chat-specific workspace for logs
|
|
39
|
+
chat_name_without_ext = os.path.splitext(
|
|
40
|
+
os.path.basename(chat_instance.chat_name))[0]
|
|
41
|
+
workspace_dir = os.path.join(
|
|
42
|
+
base_directory,
|
|
43
|
+
"ara",
|
|
44
|
+
f"{chat_name_without_ext}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Ensure workspace directory exists
|
|
48
|
+
os.makedirs(workspace_dir, exist_ok=True)
|
|
49
|
+
return os.path.abspath(workspace_dir)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def determine_base_work_dir(chat_instance):
|
|
53
|
+
"""
|
|
54
|
+
Determines the base working directory (project root).
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
chat_instance: The Chat instance
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
str: Absolute path to the project root directory
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
return os.path.dirname("./")
|
|
64
|
+
# The bwd should be the project root, not inside ara/
|
|
65
|
+
chat_dir = os.path.dirname(chat_instance.chat_name)
|
|
66
|
+
|
|
67
|
+
# Find project root by looking for 'ara' directory
|
|
68
|
+
current_dir = chat_dir
|
|
69
|
+
while True:
|
|
70
|
+
if os.path.isdir(os.path.join(current_dir, "ara")):
|
|
71
|
+
return os.path.abspath(current_dir)
|
|
72
|
+
parent_dir = os.path.dirname(current_dir)
|
|
73
|
+
if parent_dir == current_dir: # Reached filesystem root
|
|
74
|
+
# Fallback to chat directory if no 'ara' folder found
|
|
75
|
+
return os.path.abspath(chat_dir)
|
|
76
|
+
current_dir = parent_dir
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from ara_cli.commands.command import Command
|
|
2
|
+
from ara_cli.prompt_extractor import extract_responses
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class ExtractCommand(Command):
|
|
6
|
+
def __init__(self, file_name, force=False, write=False, output=None):
|
|
7
|
+
self.file_name = file_name
|
|
8
|
+
self.force = force
|
|
9
|
+
self.write = write
|
|
10
|
+
self.output = output # Callable for standard output (optional)
|
|
11
|
+
|
|
12
|
+
def execute(self, *args, **kwargs):
|
|
13
|
+
extract_responses(self.file_name, True, force=self.force, write=self.write)
|
|
14
|
+
if self.output:
|
|
15
|
+
self.output("End of extraction")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from ara_cli.commands.command import Command
|
|
2
|
+
from ara_cli.file_loaders.file_loader import FileLoaderFactory
|
|
3
|
+
from ara_cli.file_loaders.binary_file_loader import BinaryFileLoader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LoadCommand(Command):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
chat_instance,
|
|
10
|
+
file_path: str,
|
|
11
|
+
prefix: str = "",
|
|
12
|
+
suffix: str = "",
|
|
13
|
+
block_delimiter: str = "",
|
|
14
|
+
extract_images: bool = False,
|
|
15
|
+
output=None
|
|
16
|
+
):
|
|
17
|
+
self.chat = chat_instance
|
|
18
|
+
self.file_path = file_path
|
|
19
|
+
self.prefix = prefix
|
|
20
|
+
self.suffix = suffix
|
|
21
|
+
self.block_delimiter = block_delimiter
|
|
22
|
+
self.extract_images = extract_images
|
|
23
|
+
self.output = output or print
|
|
24
|
+
|
|
25
|
+
def execute(self) -> bool:
|
|
26
|
+
loader = FileLoaderFactory.create_loader(self.file_path, self.chat)
|
|
27
|
+
|
|
28
|
+
if isinstance(loader, BinaryFileLoader):
|
|
29
|
+
# Determine mime type for binary files
|
|
30
|
+
file_name_lower = self.file_path.lower()
|
|
31
|
+
mime_type = None
|
|
32
|
+
for extension, mt in FileLoaderFactory.BINARY_TYPE_MAPPING.items():
|
|
33
|
+
if file_name_lower.endswith(extension):
|
|
34
|
+
mime_type = mt
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
if not mime_type:
|
|
38
|
+
self.output(
|
|
39
|
+
f"Could not determine mime type for {self.file_path}")
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
success = loader.load(
|
|
43
|
+
self.file_path,
|
|
44
|
+
mime_type=mime_type,
|
|
45
|
+
prefix=self.prefix,
|
|
46
|
+
suffix=self.suffix
|
|
47
|
+
)
|
|
48
|
+
elif hasattr(loader, 'load'):
|
|
49
|
+
success = loader.load(
|
|
50
|
+
self.file_path,
|
|
51
|
+
prefix=self.prefix,
|
|
52
|
+
suffix=self.suffix,
|
|
53
|
+
block_delimiter=self.block_delimiter,
|
|
54
|
+
extract_images=self.extract_images
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
if success:
|
|
60
|
+
if self.extract_images and not isinstance(loader, BinaryFileLoader):
|
|
61
|
+
self.output(
|
|
62
|
+
f"Loaded contents of file {self.file_path} with images extracted")
|
|
63
|
+
else:
|
|
64
|
+
self.output(f"Loaded contents of file {self.file_path}")
|
|
65
|
+
return success
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from ara_cli.commands.command import Command
|
|
2
|
+
from ara_cli.file_loaders.binary_file_loader import BinaryFileLoader
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LoadImageCommand(Command):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
chat_instance,
|
|
9
|
+
file_path: str,
|
|
10
|
+
mime_type: str,
|
|
11
|
+
prefix: str = "",
|
|
12
|
+
suffix: str = "",
|
|
13
|
+
output=None
|
|
14
|
+
):
|
|
15
|
+
self.chat = chat_instance
|
|
16
|
+
self.file_path = file_path
|
|
17
|
+
self.mime_type = mime_type
|
|
18
|
+
self.prefix = prefix
|
|
19
|
+
self.suffix = suffix
|
|
20
|
+
self.output = output or print
|
|
21
|
+
|
|
22
|
+
def execute(self) -> bool:
|
|
23
|
+
loader = BinaryFileLoader(self.chat)
|
|
24
|
+
success = loader.load(
|
|
25
|
+
self.file_path,
|
|
26
|
+
mime_type=self.mime_type,
|
|
27
|
+
prefix=self.prefix,
|
|
28
|
+
suffix=self.suffix
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if success:
|
|
32
|
+
self.output(f"Loaded image file {self.file_path}")
|
|
33
|
+
|
|
34
|
+
return success
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from ara_cli.commands.command import Command
|
|
2
|
+
from ara_cli.artefact_reader import ArtefactReader
|
|
3
|
+
from ara_cli.file_classifier import FileClassifier
|
|
4
|
+
from ara_cli.list_filter import ListFilter, filter_list
|
|
5
|
+
from ara_cli.artefact_models.artefact_data_retrieval import (
|
|
6
|
+
artefact_content_retrieval,
|
|
7
|
+
artefact_path_retrieval,
|
|
8
|
+
artefact_tags_retrieval
|
|
9
|
+
)
|
|
10
|
+
from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReadCommand(Command):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
classifier: str,
|
|
18
|
+
artefact_name: str,
|
|
19
|
+
read_mode: str = "default",
|
|
20
|
+
list_filter: ListFilter = None,
|
|
21
|
+
output=None
|
|
22
|
+
):
|
|
23
|
+
self.classifier = classifier
|
|
24
|
+
self.artefact_name = artefact_name
|
|
25
|
+
self.read_mode = read_mode
|
|
26
|
+
self.list_filter = list_filter or ListFilter()
|
|
27
|
+
self.output = output or print
|
|
28
|
+
|
|
29
|
+
def execute(self) -> bool:
|
|
30
|
+
"""Execute the read command and return success status."""
|
|
31
|
+
file_classifier = FileClassifier(os)
|
|
32
|
+
classified_artefacts = ArtefactReader.read_artefacts()
|
|
33
|
+
|
|
34
|
+
if not self.classifier or not self.artefact_name:
|
|
35
|
+
self._filter_and_print(classified_artefacts, file_classifier)
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
artefacts = classified_artefacts.get(self.classifier, [])
|
|
39
|
+
all_artefact_names = [a.title for a in artefacts]
|
|
40
|
+
|
|
41
|
+
if self.artefact_name not in all_artefact_names:
|
|
42
|
+
suggest_close_name_matches(
|
|
43
|
+
self.artefact_name,
|
|
44
|
+
all_artefact_names
|
|
45
|
+
)
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
target_artefact = next(filter(
|
|
49
|
+
lambda x: x.title == self.artefact_name, artefacts
|
|
50
|
+
))
|
|
51
|
+
|
|
52
|
+
artefacts_by_classifier = {self.classifier: []}
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
match self.read_mode:
|
|
56
|
+
case "branch":
|
|
57
|
+
self._handle_branch_mode(
|
|
58
|
+
classified_artefacts, artefacts_by_classifier
|
|
59
|
+
)
|
|
60
|
+
case "children":
|
|
61
|
+
artefacts_by_classifier = self._handle_children_mode(
|
|
62
|
+
classified_artefacts
|
|
63
|
+
)
|
|
64
|
+
case _:
|
|
65
|
+
self._handle_default_mode(
|
|
66
|
+
target_artefact, artefacts_by_classifier
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Apply filtering and print results
|
|
70
|
+
self._filter_and_print(artefacts_by_classifier, file_classifier)
|
|
71
|
+
# filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
|
|
72
|
+
# file_classifier.print_classified_files(
|
|
73
|
+
# filtered_artefacts, print_content=True
|
|
74
|
+
# )
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self.output(f"Error reading artefact: {e}")
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def _handle_branch_mode(self, classified_artefacts, artefacts_by_classifier):
|
|
82
|
+
"""Handle branch read mode."""
|
|
83
|
+
ArtefactReader.step_through_value_chain(
|
|
84
|
+
artefact_name=self.artefact_name,
|
|
85
|
+
classifier=self.classifier,
|
|
86
|
+
artefacts_by_classifier=artefacts_by_classifier,
|
|
87
|
+
classified_artefacts=classified_artefacts
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _handle_children_mode(self, classified_artefacts):
|
|
91
|
+
"""Handle children read mode."""
|
|
92
|
+
return ArtefactReader.find_children(
|
|
93
|
+
artefact_name=self.artefact_name,
|
|
94
|
+
classifier=self.classifier,
|
|
95
|
+
classified_artefacts=classified_artefacts
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _handle_default_mode(self, target_artefact, artefacts_by_classifier):
|
|
99
|
+
"""Handle default read mode."""
|
|
100
|
+
artefacts_by_classifier[self.classifier].append(target_artefact)
|
|
101
|
+
|
|
102
|
+
def _apply_filtering(self, artefacts_by_classifier):
|
|
103
|
+
"""Apply list filtering to artefacts."""
|
|
104
|
+
return filter_list(
|
|
105
|
+
list_to_filter=artefacts_by_classifier,
|
|
106
|
+
list_filter=self.list_filter,
|
|
107
|
+
content_retrieval_strategy=artefact_content_retrieval,
|
|
108
|
+
file_path_retrieval=artefact_path_retrieval,
|
|
109
|
+
tag_retrieval=artefact_tags_retrieval
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def _filter_and_print(self, artefacts_by_classifier, file_classifier):
|
|
113
|
+
"""Apply list filtering and print results"""
|
|
114
|
+
filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
|
|
115
|
+
file_classifier.print_classified_files(
|
|
116
|
+
filtered_artefacts, print_content=True
|
|
117
|
+
)
|