Fix Windows backend test compatibility (#1384)

* Fix Windows backend test compatibility

* Preserve ACP path style on Windows

* Fix installer import ordering

* Address review comments for Windows fixes

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
Admire
2026-03-26 17:39:16 +08:00
committed by GitHub
parent b3d3287b80
commit b9583f7204
10 changed files with 141 additions and 27 deletions

View File

@@ -1,3 +1,4 @@
import posixpath
import re
from pathlib import Path
@@ -99,8 +100,8 @@ def _resolve_skills_path(path: str) -> str:
if path == skills_container:
return skills_host
relative = path[len(skills_container) :].lstrip("/")
return str(Path(skills_host) / relative) if relative else skills_host
relative = path[len(skills_container):].lstrip("/")
return _join_path_preserving_style(skills_host, relative)
def _is_acp_workspace_path(path: str) -> bool:
@@ -190,23 +191,39 @@ def _resolve_acp_workspace_path(path: str, thread_id: str | None = None) -> str:
return host_path
relative = path[len(_ACP_WORKSPACE_VIRTUAL_PATH) :].lstrip("/")
if not relative:
return host_path
resolved = _join_path_preserving_style(host_path, relative)
resolved = Path(host_path).resolve() / relative
# Ensure resolved path stays inside the ACP workspace
if "/" in host_path and "\\" not in host_path:
base_path = posixpath.normpath(host_path)
candidate_path = posixpath.normpath(resolved)
try:
if posixpath.commonpath([base_path, candidate_path]) != base_path:
raise PermissionError("Access denied: path traversal detected")
except ValueError:
raise PermissionError("Access denied: path traversal detected") from None
return resolved
resolved_path = Path(resolved).resolve()
try:
resolved.resolve().relative_to(Path(host_path).resolve())
resolved_path.relative_to(Path(host_path).resolve())
except ValueError:
raise PermissionError("Access denied: path traversal detected")
return str(resolved)
return str(resolved_path)
def _path_variants(path: str) -> set[str]:
return {path, path.replace("\\", "/"), path.replace("/", "\\")}
def _join_path_preserving_style(base: str, relative: str) -> str:
if not relative:
return base
if "/" in base and "\\" not in base:
return f"{base.rstrip('/')}/{relative}"
return str(Path(base) / relative)
def _sanitize_error(error: Exception, runtime: "ToolRuntime[ContextT, ThreadState] | None" = None) -> str:
"""Sanitize an error message to avoid leaking host filesystem paths.
@@ -249,7 +266,7 @@ def replace_virtual_path(path: str, thread_data: ThreadDataState | None) -> str:
return actual_base
if path.startswith(f"{virtual_base}/"):
rest = path[len(virtual_base) :].lstrip("/")
return str(Path(actual_base) / rest) if rest else actual_base
return _join_path_preserving_style(actual_base, rest)
return path