ls fallback: no assume src/
Browse files
src/deepagents_rl/train/swe_env.py
CHANGED
|
@@ -392,8 +392,9 @@ class DeepAgentSWEEnv:
|
|
| 392 |
# TRL appends this string to the last user message — force tool use.
|
| 393 |
return (
|
| 394 |
"\n\nCRITICAL: Use structured tool/function calls only. "
|
| 395 |
-
"Your first action MUST be
|
| 396 |
-
"
|
|
|
|
| 397 |
"Do not answer with a text-only plan. When the fix is applied, stop tools and say DONE."
|
| 398 |
)
|
| 399 |
|
|
@@ -409,17 +410,49 @@ class DeepAgentSWEEnv:
|
|
| 409 |
raise RuntimeError("DeepAgentSWEEnv tool called before reset()")
|
| 410 |
return self._backend
|
| 411 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
@_traced_tool
|
| 413 |
def ls(self, path: str = ".") -> str:
|
| 414 |
-
"""List files in a directory inside the sandbox.
|
| 415 |
|
| 416 |
Args:
|
| 417 |
-
path: Directory
|
|
|
|
| 418 |
|
| 419 |
Returns:
|
| 420 |
-
Directory listing as text.
|
| 421 |
"""
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
@_traced_tool
|
| 425 |
def glob(self, pattern: str, path: str = ".") -> str:
|
|
@@ -427,12 +460,22 @@ class DeepAgentSWEEnv:
|
|
| 427 |
|
| 428 |
Args:
|
| 429 |
pattern: Glob pattern (e.g. '**/*.py').
|
| 430 |
-
path: Root path for the search.
|
| 431 |
|
| 432 |
Returns:
|
| 433 |
Matching paths as text.
|
| 434 |
"""
|
| 435 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
|
| 437 |
@_traced_tool
|
| 438 |
def grep(self, pattern: str, path: str = ".") -> str:
|
|
@@ -440,28 +483,44 @@ class DeepAgentSWEEnv:
|
|
| 440 |
|
| 441 |
Args:
|
| 442 |
pattern: Regex pattern to search for.
|
| 443 |
-
path: Root path for the search.
|
| 444 |
|
| 445 |
Returns:
|
| 446 |
Matching lines as text.
|
| 447 |
"""
|
| 448 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
|
| 450 |
@_traced_tool
|
| 451 |
def read_file(self, file_path: str, offset: int = 0, limit: int = 200) -> str:
|
| 452 |
"""Read a text file from the sandbox.
|
| 453 |
|
| 454 |
Args:
|
| 455 |
-
file_path: Path relative to the repo root.
|
| 456 |
offset: Starting line offset.
|
| 457 |
limit: Max number of lines to return.
|
| 458 |
|
| 459 |
Returns:
|
| 460 |
File contents as text.
|
| 461 |
"""
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
|
| 466 |
@_traced_tool
|
| 467 |
def write_file(self, file_path: str, content: str) -> str:
|
|
|
|
| 392 |
# TRL appends this string to the last user message — force tool use.
|
| 393 |
return (
|
| 394 |
"\n\nCRITICAL: Use structured tool/function calls only. "
|
| 395 |
+
"Your first action MUST be: ls with path='.' (repo root). "
|
| 396 |
+
"Do NOT call ls on 'src' — that folder often does not exist. "
|
| 397 |
+
"Then read_file/grep using paths from that listing, then edit_file/write_file. "
|
| 398 |
"Do not answer with a text-only plan. When the fix is applied, stop tools and say DONE."
|
| 399 |
)
|
| 400 |
|
|
|
|
| 410 |
raise RuntimeError("DeepAgentSWEEnv tool called before reset()")
|
| 411 |
return self._backend
|
| 412 |
|
| 413 |
+
@staticmethod
|
| 414 |
+
def _norm_path(path: str | None) -> str:
|
| 415 |
+
p = (path or ".").strip()
|
| 416 |
+
if p in ("", "/", "./", ".\\"):
|
| 417 |
+
return "."
|
| 418 |
+
# virtual_mode often wants leading slash for absolute-in-sandbox paths
|
| 419 |
+
if p.startswith("./"):
|
| 420 |
+
p = p[2:]
|
| 421 |
+
return p or "."
|
| 422 |
+
|
| 423 |
+
def _is_missing(self, result_obj: Any, text: str) -> bool:
|
| 424 |
+
err = getattr(result_obj, "error", None)
|
| 425 |
+
if err and "path_not_found" in str(err).lower():
|
| 426 |
+
return True
|
| 427 |
+
return "path_not_found" in (text or "").lower()
|
| 428 |
+
|
| 429 |
+
def _root_listing(self) -> str:
|
| 430 |
+
backend = self._require_backend()
|
| 431 |
+
return _result_to_str(backend.ls("."))
|
| 432 |
+
|
| 433 |
@_traced_tool
|
| 434 |
def ls(self, path: str = ".") -> str:
|
| 435 |
+
"""List files in a directory inside the sandbox repo root.
|
| 436 |
|
| 437 |
Args:
|
| 438 |
+
path: Directory relative to the repo root. Use "." for the root.
|
| 439 |
+
Do NOT assume a "src/" folder exists — many repos have none.
|
| 440 |
|
| 441 |
Returns:
|
| 442 |
+
Directory listing as text (falls back to repo root if path missing).
|
| 443 |
"""
|
| 444 |
+
backend = self._require_backend()
|
| 445 |
+
path = self._norm_path(path)
|
| 446 |
+
result = backend.ls(path)
|
| 447 |
+
out = _result_to_str(result)
|
| 448 |
+
if self._is_missing(result, out):
|
| 449 |
+
root = self._root_listing()
|
| 450 |
+
return (
|
| 451 |
+
f"Path '{path}' was not found in this repo sandbox.\n"
|
| 452 |
+
f"Tip: there is often no 'src/' directory. Start with path='.' (repo root).\n"
|
| 453 |
+
f"Repo root listing:\n{root}"
|
| 454 |
+
)
|
| 455 |
+
return out
|
| 456 |
|
| 457 |
@_traced_tool
|
| 458 |
def glob(self, pattern: str, path: str = ".") -> str:
|
|
|
|
| 460 |
|
| 461 |
Args:
|
| 462 |
pattern: Glob pattern (e.g. '**/*.py').
|
| 463 |
+
path: Root path for the search (use '.' for repo root).
|
| 464 |
|
| 465 |
Returns:
|
| 466 |
Matching paths as text.
|
| 467 |
"""
|
| 468 |
+
backend = self._require_backend()
|
| 469 |
+
path = self._norm_path(path)
|
| 470 |
+
result = backend.glob(pattern, path)
|
| 471 |
+
out = _result_to_str(result)
|
| 472 |
+
if self._is_missing(result, out):
|
| 473 |
+
root = self._root_listing()
|
| 474 |
+
return (
|
| 475 |
+
f"Path '{path}' not found for glob. Retry with path='.'.\n"
|
| 476 |
+
f"Repo root listing:\n{root}"
|
| 477 |
+
)
|
| 478 |
+
return out
|
| 479 |
|
| 480 |
@_traced_tool
|
| 481 |
def grep(self, pattern: str, path: str = ".") -> str:
|
|
|
|
| 483 |
|
| 484 |
Args:
|
| 485 |
pattern: Regex pattern to search for.
|
| 486 |
+
path: Root path for the search (use '.' for repo root).
|
| 487 |
|
| 488 |
Returns:
|
| 489 |
Matching lines as text.
|
| 490 |
"""
|
| 491 |
+
backend = self._require_backend()
|
| 492 |
+
path = self._norm_path(path)
|
| 493 |
+
result = backend.grep(pattern, path)
|
| 494 |
+
out = _result_to_str(result)
|
| 495 |
+
if self._is_missing(result, out):
|
| 496 |
+
return (
|
| 497 |
+
f"Path '{path}' not found for grep. Retry with path='.' (repo root).\n"
|
| 498 |
+
f"Repo root listing:\n{self._root_listing()}"
|
| 499 |
+
)
|
| 500 |
+
return out
|
| 501 |
|
| 502 |
@_traced_tool
|
| 503 |
def read_file(self, file_path: str, offset: int = 0, limit: int = 200) -> str:
|
| 504 |
"""Read a text file from the sandbox.
|
| 505 |
|
| 506 |
Args:
|
| 507 |
+
file_path: Path relative to the repo root (discover via ls/glob first).
|
| 508 |
offset: Starting line offset.
|
| 509 |
limit: Max number of lines to return.
|
| 510 |
|
| 511 |
Returns:
|
| 512 |
File contents as text.
|
| 513 |
"""
|
| 514 |
+
backend = self._require_backend()
|
| 515 |
+
file_path = self._norm_path(file_path)
|
| 516 |
+
result = backend.read(file_path, offset=offset, limit=limit)
|
| 517 |
+
out = _result_to_str(result)
|
| 518 |
+
if self._is_missing(result, out):
|
| 519 |
+
return (
|
| 520 |
+
f"File '{file_path}' not found. Use ls path='.' or glob to discover real paths.\n"
|
| 521 |
+
f"Repo root listing:\n{self._root_listing()}"
|
| 522 |
+
)
|
| 523 |
+
return out
|
| 524 |
|
| 525 |
@_traced_tool
|
| 526 |
def write_file(self, file_path: str, content: str) -> str:
|