mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
core/tools+config: name the access mode in the workspace escape error
This commit is contained in:
@@ -40,8 +40,9 @@ DEFAULT_TOOL_DIRECTIVES = """\
|
|||||||
### Workspace
|
### Workspace
|
||||||
- Explore with `tree`/`listdir`/`glob_paths`/`regex_files`/`read_file` when unsure.
|
- Explore with `tree`/`listdir`/`glob_paths`/`regex_files`/`read_file` when unsure.
|
||||||
- Stay under the workspace (or a path from `new_temporary_workspace`). Respect path denylists.
|
- Stay under the workspace (or a path from `new_temporary_workspace`). Respect path denylists.
|
||||||
- Need a path outside the workspace? Call `request_directory_access` \
|
- Need a path outside the workspace? Call `request_directory_access` with the \
|
||||||
(human approves; denylists apply) instead of retrying an escaped path.
|
`mode` the escaped-path error names (human approves; denylists apply) instead of \
|
||||||
|
retrying that path.
|
||||||
|
|
||||||
### Files
|
### Files
|
||||||
- Prefer file tools over shell. `edit_replace` fails usually means a bad match — \
|
- Prefer file tools over shell. `edit_replace` fails usually means a bad match — \
|
||||||
|
|||||||
@@ -254,11 +254,11 @@ async def request_directory_access(path: str, reason: str = "", mode: str = "rea
|
|||||||
"""Request human approval to use a directory (or exact file) outside the workspace.
|
"""Request human approval to use a directory (or exact file) outside the workspace.
|
||||||
|
|
||||||
Call this instead of retrying a path that failed with ``path escapes
|
Call this instead of retrying a path that failed with ``path escapes
|
||||||
workspace root``. ``mode`` is ``read`` (inspect files), ``write`` (also
|
workspace root``, passing the ``mode`` that error names. ``mode`` is ``read``
|
||||||
modify files) or ``exec`` (also use it as ``run_argv`` / PTY cwd); the human
|
(inspect files), ``write`` (also modify files) or ``exec`` (also use it as
|
||||||
may approve a different level. Approval lasts for the session (or for this
|
``run_argv`` / PTY cwd); the human may approve a different level. Approval
|
||||||
process when ``--yes`` auto-approves). Paths matching the configured
|
lasts for the session (or for this process when ``--yes`` auto-approves).
|
||||||
denylist can never be granted.
|
Paths matching the configured denylist can never be granted.
|
||||||
"""
|
"""
|
||||||
requested = parse_access_mode(mode)
|
requested = parse_access_mode(mode)
|
||||||
if requested is None:
|
if requested is None:
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ def resolve_path(path: str | Path, *, required: AccessMode = AccessMode.WRITE) -
|
|||||||
if not _under_any_root(resolved, instance, policy) and not _path_grant_covers(resolved, required):
|
if not _under_any_root(resolved, instance, policy) and not _path_grant_covers(resolved, required):
|
||||||
msg = (
|
msg = (
|
||||||
f"path escapes workspace root ({root}): {path}; "
|
f"path escapes workspace root ({root}): {path}; "
|
||||||
"call request_directory_access to request access to a directory outside the workspace"
|
f"call request_directory_access with mode={required.name.lower()!r} to request access outside the workspace"
|
||||||
)
|
)
|
||||||
raise WorkspaceError(msg)
|
raise WorkspaceError(msg)
|
||||||
# Normalize separators so denylist entries like ``/secrets/`` match on Windows.
|
# Normalize separators so denylist entries like ``/secrets/`` match on Windows.
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ def test_escape_error_hints_request_tool(workspace: object) -> None:
|
|||||||
_ = resolve_path("../outside")
|
_ = resolve_path("../outside")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("required", [AccessMode.READ, AccessMode.WRITE, AccessMode.EXEC])
|
||||||
|
def test_escape_error_names_the_mode_to_request(workspace: object, required: AccessMode) -> None:
|
||||||
|
"""The refusal carries the lowest access mode that would cover the call."""
|
||||||
|
del workspace
|
||||||
|
with pytest.raises(WorkspaceError, match=f"mode='{required.name.lower()}'"):
|
||||||
|
_ = resolve_path("../outside", required=required)
|
||||||
|
|
||||||
|
|
||||||
def test_access_mode_parse_and_covers() -> None:
|
def test_access_mode_parse_and_covers() -> None:
|
||||||
assert parse_access_mode(" Read ") is AccessMode.READ
|
assert parse_access_mode(" Read ") is AccessMode.READ
|
||||||
assert parse_access_mode("EXEC") is AccessMode.EXEC
|
assert parse_access_mode("EXEC") is AccessMode.EXEC
|
||||||
|
|||||||
Reference in New Issue
Block a user