@@ -259,11 +259,16 @@ class RelativePathContextHook(BaseToolHook): |
| 259 | 259 | |
| 260 | 260 | require_existing = context.tool_call.name in {"read", "glob", "grep", "edit", "patch"} |
| 261 | 261 | resolved: str | None = None |
| 262 | + injected_messages: list[str] = [] |
| 262 | 263 | if raw_path.startswith("/"): |
| 263 | 264 | resolved = self._resolve_workspace_mirror_path( |
| 264 | 265 | raw_path, |
| 265 | 266 | require_existing=require_existing, |
| 266 | 267 | ) |
| 268 | + if resolved is not None: |
| 269 | + injected_messages.append( |
| 270 | + self._workspace_mirror_correction_message(raw_path, resolved) |
| 271 | + ) |
| 267 | 272 | elif not raw_path.startswith("~"): |
| 268 | 273 | resolved = self._resolve_recent_context_path( |
| 269 | 274 | raw_path, |
@@ -274,7 +279,10 @@ class RelativePathContextHook(BaseToolHook): |
| 274 | 279 | |
| 275 | 280 | updated_arguments = dict(arguments) |
| 276 | 281 | updated_arguments[argument_key] = resolved |
| 277 | | - return HookResult(updated_arguments=updated_arguments) |
| 282 | + return HookResult( |
| 283 | + updated_arguments=updated_arguments, |
| 284 | + injected_messages=injected_messages, |
| 285 | + ) |
| 278 | 286 | |
| 279 | 287 | def _argument_key(self, tool_name: str) -> str | None: |
| 280 | 288 | if tool_name in self._FILE_TOOLS: |
@@ -356,6 +364,29 @@ class RelativePathContextHook(BaseToolHook): |
| 356 | 364 | return str(remapped) |
| 357 | 365 | return None |
| 358 | 366 | |
| 367 | + def _workspace_mirror_correction_message(self, raw_path: str, resolved_path: str) -> str: |
| 368 | + raw_name = Path(str(raw_path)).name or str(raw_path) |
| 369 | + resolved_root = self._describe_anchor_root(resolved_path) |
| 370 | + return ( |
| 371 | + "[Path anchor correction] A repo-local mirror path was remapped to the established " |
| 372 | + f"output root under `{resolved_root}`. Keep future file/search tool calls on that " |
| 373 | + f"external root and use `{raw_name}` there instead of re-anchoring work to the " |
| 374 | + "workspace checkout." |
| 375 | + ) |
| 376 | + |
| 377 | + def _describe_anchor_root(self, path_value: str) -> str: |
| 378 | + resolved = Path(path_value).expanduser() |
| 379 | + try: |
| 380 | + candidate = resolved.resolve(strict=False) |
| 381 | + except Exception: |
| 382 | + candidate = resolved |
| 383 | + |
| 384 | + parts = candidate.parts |
| 385 | + if "Loader" in parts: |
| 386 | + loader_index = parts.index("Loader") |
| 387 | + return str(Path(*parts[: loader_index + 1])) |
| 388 | + return str(candidate.parent) |
| 389 | + |
| 359 | 390 | |
| 360 | 391 | _OBSERVATION_TOOLS = frozenset({"read", "glob", "grep", "bash"}) |
| 361 | 392 | _MUTATION_TOOLS = frozenset({"write", "edit", "patch", "bash"}) |