Sisyphus agent recreated in LangChain to figure out how it works and how to use it
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Project detection and build/test tools.
|
||||
|
||||
These mirror Loki's .shared/utils.sh detect_project() heuristic and the
|
||||
sisyphus/coder tools.sh run_build / run_tests / verify_build commands.
|
||||
|
||||
Loki Concept Mapping:
|
||||
Loki uses a heuristic cascade: check for Cargo.toml → go.mod → package.json
|
||||
etc., then falls back to an LLM call for unknown projects. We replicate the
|
||||
heuristic portion here. The LLM fallback is omitted since the agents
|
||||
themselves can reason about unknown project types.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from langchain_core.tools import tool
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Project detection (mirrors _detect_heuristic in utils.sh)
|
||||
# ---------------------------------------------------------------------------
|
||||
_HEURISTICS: list[tuple[str, dict[str, str]]] = [
|
||||
("Cargo.toml", {"type": "rust", "build": "cargo build", "test": "cargo test", "check": "cargo check"}),
|
||||
("go.mod", {"type": "go", "build": "go build ./...", "test": "go test ./...", "check": "go vet ./..."}),
|
||||
("package.json", {"type": "nodejs", "build": "npm run build", "test": "npm test", "check": "npm run lint"}),
|
||||
("pyproject.toml", {"type": "python", "build": "", "test": "pytest", "check": "ruff check ."}),
|
||||
("pom.xml", {"type": "java", "build": "mvn compile", "test": "mvn test", "check": "mvn verify"}),
|
||||
("Makefile", {"type": "make", "build": "make build", "test": "make test", "check": "make lint"}),
|
||||
]
|
||||
|
||||
|
||||
def detect_project(directory: str) -> dict[str, str]:
|
||||
"""Detect project type and return build/test commands."""
|
||||
for marker, info in _HEURISTICS:
|
||||
if os.path.exists(os.path.join(directory, marker)):
|
||||
return info
|
||||
return {"type": "unknown", "build": "", "test": "", "check": ""}
|
||||
|
||||
|
||||
@tool
|
||||
def get_project_info(directory: str = ".") -> str:
|
||||
"""Detect the project type and show structure overview.
|
||||
|
||||
Args:
|
||||
directory: Project root directory.
|
||||
"""
|
||||
directory = os.path.expanduser(directory)
|
||||
info = detect_project(directory)
|
||||
result = f"Project: {os.path.abspath(directory)}\n"
|
||||
result += f"Type: {info['type']}\n"
|
||||
result += f"Build: {info['build'] or '(none)'}\n"
|
||||
result += f"Test: {info['test'] or '(none)'}\n"
|
||||
result += f"Check: {info['check'] or '(none)'}\n"
|
||||
return result
|
||||
|
||||
|
||||
def _run_project_command(directory: str, command_key: str) -> str:
|
||||
"""Run a detected project command (build/test/check)."""
|
||||
directory = os.path.expanduser(directory)
|
||||
info = detect_project(directory)
|
||||
cmd = info.get(command_key, "")
|
||||
|
||||
if not cmd:
|
||||
return f"No {command_key} command detected for this project."
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=directory,
|
||||
timeout=300,
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
status = "SUCCESS" if result.returncode == 0 else f"FAILED (exit {result.returncode})"
|
||||
return f"Running: {cmd}\n\n{output}\n\n{command_key.upper()}: {status}"
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"{command_key.upper()}: TIMEOUT after 300s"
|
||||
except Exception as e:
|
||||
return f"{command_key.upper()}: ERROR — {e}"
|
||||
|
||||
|
||||
@tool
|
||||
def run_build(directory: str = ".") -> str:
|
||||
"""Run the project's build command.
|
||||
|
||||
Args:
|
||||
directory: Project root directory.
|
||||
"""
|
||||
return _run_project_command(directory, "build")
|
||||
|
||||
|
||||
@tool
|
||||
def run_tests(directory: str = ".") -> str:
|
||||
"""Run the project's test suite.
|
||||
|
||||
Args:
|
||||
directory: Project root directory.
|
||||
"""
|
||||
return _run_project_command(directory, "test")
|
||||
|
||||
|
||||
@tool
|
||||
def verify_build(directory: str = ".") -> str:
|
||||
"""Run the project's check/lint command to verify correctness.
|
||||
|
||||
Args:
|
||||
directory: Project root directory.
|
||||
"""
|
||||
return _run_project_command(directory, "check")
|
||||
|
||||
|
||||
@tool
|
||||
def execute_command(command: str, directory: str = ".") -> str:
|
||||
"""Execute a shell command and return its output.
|
||||
|
||||
Args:
|
||||
command: Shell command to execute.
|
||||
directory: Working directory.
|
||||
"""
|
||||
directory = os.path.expanduser(directory)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=directory,
|
||||
timeout=120,
|
||||
)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
if result.returncode != 0:
|
||||
return f"Command failed (exit {result.returncode}):\n{output}"
|
||||
return output or "(no output)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return "Command timed out after 120s."
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
Reference in New Issue
Block a user