adelie-ai 0.2.2 → 0.2.3
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.
- package/adelie/agents/runner_ai.py +30 -0
- package/adelie/orchestrator.py +34 -6
- package/adelie/web_search.py +6 -0
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
import json
|
|
19
19
|
import re
|
|
20
20
|
import shlex
|
|
21
|
+
import shutil
|
|
21
22
|
import subprocess
|
|
22
23
|
import sys
|
|
23
24
|
from datetime import datetime
|
|
@@ -63,6 +64,29 @@ EXEC_TIMEOUT_RUN = 10 # Short timeout — we just check if it starts
|
|
|
63
64
|
EXEC_TIMEOUT_DEPLOY = 180
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
def _detect_available_tools() -> str:
|
|
68
|
+
"""Check which CLI tools are actually installed on this system."""
|
|
69
|
+
tools_to_check = [
|
|
70
|
+
"npm", "npx", "node", "yarn", "pnpm",
|
|
71
|
+
"pip", "pip3", "python", "python3",
|
|
72
|
+
"docker", "docker-compose", "podman",
|
|
73
|
+
"cargo", "go", "make",
|
|
74
|
+
"vite", "next",
|
|
75
|
+
]
|
|
76
|
+
available = []
|
|
77
|
+
unavailable = []
|
|
78
|
+
for tool in tools_to_check:
|
|
79
|
+
if shutil.which(tool):
|
|
80
|
+
available.append(tool)
|
|
81
|
+
else:
|
|
82
|
+
unavailable.append(tool)
|
|
83
|
+
|
|
84
|
+
lines = [f"Available: {', '.join(available)}"]
|
|
85
|
+
if unavailable:
|
|
86
|
+
lines.append(f"NOT INSTALLED (do NOT use): {', '.join(unavailable)}")
|
|
87
|
+
return "\n".join(lines)
|
|
88
|
+
|
|
89
|
+
|
|
66
90
|
def _diagnose_build_error(stderr: str, stdout: str = "") -> list[dict]:
|
|
67
91
|
"""
|
|
68
92
|
Parse build error output to extract actionable file/line/error information.
|
|
@@ -397,7 +421,13 @@ def run_pipeline(
|
|
|
397
421
|
except Exception:
|
|
398
422
|
pass
|
|
399
423
|
|
|
424
|
+
# Detect available tools on this system
|
|
425
|
+
tools_info = _detect_available_tools()
|
|
426
|
+
|
|
400
427
|
user_prompt = (
|
|
428
|
+
f"## System Tools\n{tools_info}\n\n"
|
|
429
|
+
f"CRITICAL: Only generate commands using tools listed as 'Available' above.\n"
|
|
430
|
+
f"Do NOT generate commands for tools listed as 'NOT INSTALLED'.\n\n"
|
|
401
431
|
f"## Project Files\n\n"
|
|
402
432
|
+ "\n\n".join(file_list[:20])
|
|
403
433
|
+ f"\n\n## Max Tier: {max_tier}\n"
|
package/adelie/orchestrator.py
CHANGED
|
@@ -61,7 +61,6 @@ class Orchestrator:
|
|
|
61
61
|
self.goal = goal or "Autonomously develop and improve the project"
|
|
62
62
|
self.phase = phase
|
|
63
63
|
self.state = LoopState.NORMAL
|
|
64
|
-
self.loop_iteration = 0
|
|
65
64
|
self.last_expert_output: dict | None = None
|
|
66
65
|
self.last_error: str | None = None
|
|
67
66
|
self._running = True
|
|
@@ -71,6 +70,12 @@ class Orchestrator:
|
|
|
71
70
|
self._phase_recommendation: str | None = None # Recommended next phase
|
|
72
71
|
self._pause_requested = False # Controlled by interactive CLI
|
|
73
72
|
|
|
73
|
+
# Restore persisted state from config.json
|
|
74
|
+
self.loop_iteration = 0
|
|
75
|
+
self._test_pass_history: list[bool] = []
|
|
76
|
+
self._review_score_history: list[int] = []
|
|
77
|
+
self._restore_state()
|
|
78
|
+
|
|
74
79
|
# Loop detection
|
|
75
80
|
self._loop_detector = LoopDetector()
|
|
76
81
|
|
|
@@ -89,9 +94,7 @@ class Orchestrator:
|
|
|
89
94
|
# Process supervisor for spawned commands
|
|
90
95
|
self.supervisor = ProcessSupervisor(max_concurrent=5)
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
self._test_pass_history: list[bool] = [] # last N test results
|
|
94
|
-
self._review_score_history: list[int] = [] # last N review scores
|
|
97
|
+
|
|
95
98
|
|
|
96
99
|
# Graceful shutdown on Ctrl+C or SIGTERM
|
|
97
100
|
signal.signal(signal.SIGINT, self._handle_signal)
|
|
@@ -302,16 +305,38 @@ class Orchestrator:
|
|
|
302
305
|
return rule["next"].value
|
|
303
306
|
return None
|
|
304
307
|
|
|
305
|
-
def
|
|
306
|
-
"""Persist current phase to .adelie/config.json."""
|
|
308
|
+
def _save_state(self) -> None:
|
|
309
|
+
"""Persist current phase, loop_iteration, and quality history to .adelie/config.json."""
|
|
307
310
|
config_path = WORKSPACE_PATH.parent / "config.json"
|
|
308
311
|
if config_path.exists():
|
|
309
312
|
cfg_data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
310
313
|
else:
|
|
311
314
|
cfg_data = {}
|
|
312
315
|
cfg_data["phase"] = self.phase
|
|
316
|
+
cfg_data["loop_iteration"] = self.loop_iteration
|
|
317
|
+
cfg_data["test_pass_history"] = self._test_pass_history[-10:]
|
|
318
|
+
cfg_data["review_score_history"] = self._review_score_history[-10:]
|
|
313
319
|
config_path.write_text(json.dumps(cfg_data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
314
320
|
|
|
321
|
+
def _save_phase(self) -> None:
|
|
322
|
+
"""Persist current phase (alias for _save_state for backward compat)."""
|
|
323
|
+
self._save_state()
|
|
324
|
+
|
|
325
|
+
def _restore_state(self) -> None:
|
|
326
|
+
"""Restore loop_iteration and quality history from config.json."""
|
|
327
|
+
config_path = WORKSPACE_PATH.parent / "config.json"
|
|
328
|
+
if not config_path.exists():
|
|
329
|
+
return
|
|
330
|
+
try:
|
|
331
|
+
cfg_data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
332
|
+
self.loop_iteration = cfg_data.get("loop_iteration", 0)
|
|
333
|
+
self._test_pass_history = cfg_data.get("test_pass_history", [])
|
|
334
|
+
self._review_score_history = cfg_data.get("review_score_history", [])
|
|
335
|
+
if self.loop_iteration > 0:
|
|
336
|
+
console.print(f"[dim] ↻ Resumed from loop #{self.loop_iteration}[/dim]")
|
|
337
|
+
except Exception:
|
|
338
|
+
pass
|
|
339
|
+
|
|
315
340
|
def _write_error_to_kb(self, error: Exception | str) -> None:
|
|
316
341
|
"""Directly write an error file to the KB errors/ folder."""
|
|
317
342
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
@@ -1378,6 +1403,9 @@ class Orchestrator:
|
|
|
1378
1403
|
"state": self.state.value,
|
|
1379
1404
|
})
|
|
1380
1405
|
|
|
1406
|
+
# Persist state for resume after Ctrl+C
|
|
1407
|
+
self._save_state()
|
|
1408
|
+
|
|
1381
1409
|
def pause(self) -> None:
|
|
1382
1410
|
"""Request the orchestrator to pause before the next cycle."""
|
|
1383
1411
|
self._pause_requested = True
|
package/adelie/web_search.py
CHANGED
|
@@ -21,6 +21,7 @@ from adelie.config import (
|
|
|
21
21
|
GEMINI_API_KEY,
|
|
22
22
|
GEMINI_MODEL,
|
|
23
23
|
LLM_PROVIDER,
|
|
24
|
+
OLLAMA_API_KEY,
|
|
24
25
|
OLLAMA_BASE_URL,
|
|
25
26
|
OLLAMA_MODEL,
|
|
26
27
|
)
|
|
@@ -201,8 +202,13 @@ Provide a detailed, factual answer. Note that you don't have access to
|
|
|
201
202
|
real-time information, so state clearly when information might be outdated."""
|
|
202
203
|
|
|
203
204
|
try:
|
|
205
|
+
headers = {"Content-Type": "application/json"}
|
|
206
|
+
if OLLAMA_API_KEY:
|
|
207
|
+
headers["Authorization"] = f"Bearer {OLLAMA_API_KEY}"
|
|
208
|
+
|
|
204
209
|
resp = requests.post(
|
|
205
210
|
url,
|
|
211
|
+
headers=headers,
|
|
206
212
|
json={
|
|
207
213
|
"model": use_model,
|
|
208
214
|
"messages": [
|