From fb77fd676b8855526820b3bdcfa584e0aa20bf29 Mon Sep 17 00:00:00 2001 From: worldmozara Date: Mon, 26 Oct 2026 00:52:28 +0800 Subject: [PATCH] core/tools: point a refused write at a temporary workspace A path outside the workspace is usually rejected while the model only wants a scratch file: the refusal named the grant tool, so the model had to ask a human for access it never needed. A write now also names `new_temporary_workspace`, whose path needs no grant. Reads and commands keep the grant hint alone. --- src/plyngent/tools/workspace.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plyngent/tools/workspace.py b/src/plyngent/tools/workspace.py index 67eb4de..16b54c9 100644 --- a/src/plyngent/tools/workspace.py +++ b/src/plyngent/tools/workspace.py @@ -445,9 +445,17 @@ def resolve_path(path: str | Path, *, required: AccessMode = AccessMode.WRITE) - candidate = root / candidate resolved = candidate.expanduser().resolve() if not _under_any_root(resolved, instance, policy) and not _path_grant_covers(resolved, required): + # A refused *write* is often a scratch file the model meant to keep in + # the workspace: name that way out too, next to asking the human. + scratch = ( + "; if the file is only scratch, call new_temporary_workspace and write under the absolute path it returns" + if required is AccessMode.WRITE + else "" + ) msg = ( f"path escapes workspace root ({root}): {path}; " f"call request_directory_access with mode={required.name.lower()!r} to request access outside the workspace" + f"{scratch}" ) raise WorkspaceError(msg) # Normalize separators so denylist entries like ``/secrets/`` match on Windows.