| 278 | return "ok" |
| 279 | |
| 280 | def allowed_roots(self) -> tuple[Path, ...]: |
| 281 | roots: list[Path] = [self.workspace_root] |
| 282 | roots.extend(self.additional_working_directories) |
| 283 | # Session-granted directories from accepted permission updates. |
| 284 | # `PermissionUpdateAddDirectories` (e.g. the user choosing "allow all |
| 285 | # edits in <dir>/ during this session" for an out-of-cwd file) writes |
| 286 | # `ToolPermissionContext.additional_working_directories`; without folding |
| 287 | # those in here the grant never reaches `ensure_allowed_path` and the |
| 288 | # next edit/read in that directory still fails. Resolved so the |
| 289 | # /tmp → /private/tmp (macOS) match holds. |
| 290 | try: |
| 291 | for dir_path in self.permission_context.additional_working_directories.keys(): |
| 292 | try: |
| 293 | roots.append(Path(dir_path).resolve()) |
| 294 | except OSError: |
| 295 | roots.append(Path(dir_path)) |
| 296 | except Exception: |
| 297 | pass |
| 298 | # The session's tool-results spill dir is an internal path the runtime |
| 299 | # writes large tool results to and then points the model back at (e.g. a |
| 300 | # workflow subagent told to Read the offloaded result). Reading it back |
| 301 | # must be allowed even though it sits outside workspace_root. Resolved so |
| 302 | # the /tmp → /private/tmp (macOS) match holds against the resolved path. |
| 303 | try: |
| 304 | from src.services.tool_execution.tool_result_persistence import ( |
| 305 | resolve_tool_results_dir, |
| 306 | ) |
| 307 | |
| 308 | roots.append(resolve_tool_results_dir(self).resolve()) |
| 309 | except Exception: |
| 310 | pass |
| 311 | return tuple(roots) |
| 312 | |
| 313 | def ensure_allowed_path(self, path: str | Path) -> Path: |
| 314 | p = Path(path).expanduser() if isinstance(path, str) else path.expanduser() |