From 602d1595d80e0c2ea06c9dacb72cefad964fa93e Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 4 Sep 2026 22:10:42 +0800 Subject: [PATCH] docs: keep file examples inside workspace --- README.md | 4 ++++ prompts/examples.md | 10 +++++----- tests/test_tools.py | 24 ++++++++++++++++++++++++ tools.py | 4 ++-- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9b791b49..bb68b3ec 100644 --- a/README.md +++ b/README.md @@ -361,6 +361,10 @@ Short aliases work too — `bash`, `cmd`, `sh` → `run_cmd`; `status`, `diff`, | `run_cmd` | Execute shell command | `"python --version"` | | `execute_terminal_command` | Alias for `run_cmd` | `"python --version"` | +File and directory paths are resolved inside the active workspace. Use a +relative path such as `notes.txt` in file examples; home-directory and Desktop +paths are rejected by the workspace boundary. + `run_cmd` prepends the `bin` directory of the Python interpreter running OpenKyrozen, so bare `python` and `pip` resolve inside the active virtual environment even when the parent shell was not activated. Explicit interpreter diff --git a/prompts/examples.md b/prompts/examples.md index e9ad939e..0576b14f 100644 --- a/prompts/examples.md +++ b/prompts/examples.md @@ -2,12 +2,12 @@ ## Example 1: Creating a file -**User:** Create a file on my desktop called `notes.txt` with the content "Hello from the agent." +**User:** Create `notes.txt` in the active workspace with the content "Hello from the agent." **Assistant:** -The user wants a file on the desktop. I will use the write_file tool. +The user wants a file in the active workspace. I will use the write_file tool. ```json -{"action": "write_file", "args": "~/Desktop/notes.txt|Hello from the agent."} +{"action": "write_file", "args": "notes.txt|Hello from the agent."} ``` --- @@ -26,10 +26,10 @@ The user wants real-time price data. I need to search the web. ## Example 3: Reading a file -**User:** Read the file ~/Desktop/notes.txt +**User:** Read the file `notes.txt` from the active workspace **Assistant:** The user wants to see the contents of a file. I will use read_file. ```json -{"action": "read_file", "args": "~/Desktop/notes.txt"} +{"action": "read_file", "args": "notes.txt"} ``` diff --git a/tests/test_tools.py b/tests/test_tools.py index 410285e1..f7481452 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,3 +1,5 @@ +import json +import re import tempfile import subprocess import unittest @@ -35,6 +37,28 @@ def test_file_tools_stay_inside_workspace(self): finally: set_workspace_root(original_root) + def test_documented_file_examples_work_inside_a_temporary_workspace(self): + examples_path = Path(__file__).parents[1] / "prompts" / "examples.md" + blocks = re.findall(r"```json\n(.*?)\n```", examples_path.read_text(encoding="utf-8"), re.DOTALL) + actions = [json.loads(block) for block in blocks] + write_action = next(item for item in actions if item.get("action") == "write_file") + read_action = next(item for item in actions if item.get("action") == "read_file") + write_path, write_content = write_action["args"].split("|", 1) + self.assertFalse(Path(write_path).is_absolute()) + self.assertFalse(write_path.startswith("~")) + self.assertEqual(read_action["args"], write_path) + + original_root = Path.cwd() + with tempfile.TemporaryDirectory() as directory: + root = Path(directory).resolve() + set_workspace_root(root) + try: + self.assertIn("Wrote", write_file(write_action["args"])) + self.assertEqual(read_file(read_action["args"]), write_content) + self.assertEqual((root / write_path).read_text(encoding="utf-8"), write_content) + finally: + set_workspace_root(original_root) + def test_capability_profiles_keep_rich_access_without_irreversible_reset(self): workspace = allowed_tool_names(AVAILABLE_TOOLS, "workspace") full = allowed_tool_names(AVAILABLE_TOOLS, "full") diff --git a/tools.py b/tools.py index 90a55ff0..f323e040 100644 --- a/tools.py +++ b/tools.py @@ -103,7 +103,7 @@ def _active_command_env() -> dict[str, str]: def write_file(args: str) -> str: """ Write content to a file. Args format: "path|content". - Supports ~ for user home (e.g. ~/Desktop/file.txt). + The path must be relative to the active workspace (e.g. "notes.txt"). """ try: parts = args.split("|", 1) @@ -122,7 +122,7 @@ def write_file(args: str) -> str: def read_file(args: str) -> str: """ Read content from a file. Args format: "path". - Supports ~ for user home. + The path must be relative to the active workspace (e.g. "notes.txt"). """ try: raw_path = args.strip()