"""工作文件夹硬沙箱(P1)。

工作区对话模式下，把「写」限定在用户选的工作文件夹 F 内：WriteFile / EditFile
的目标路径若落在 F 之外，直接拒绝并把原因返回给模型，让它改到 F 内。

为什么需要:仅靠 systemPrompt 提示"就地在 F 操作"挡不住模型——实测过工作区助手
把产物写到了 F 之外的另一个 run 目录。工具层硬拦是唯一可靠手段。

作用范围:只拦【写】(write_file/edit_file)。读不拦(读不破坏，且常需交叉引用
F 之外的文件)。Bash 是真正的 shell、无法在工具层可靠沙箱，靠 CWD=F + guard
(dangerousCommandBlock) 兜。

线程模型:与 shell_tools._session_cwd 一致——模块级 + 锁。单用户桌面 run 通常串行。
未设 root(None)= 不限制(专项智能体写各自 run 目录，不受影响)。
"""
from __future__ import annotations

import os
import threading
from typing import Optional, Tuple

_root: Optional[str] = None
_lock = threading.Lock()


def set_sandbox_root(path: str | None) -> None:
    """设置写沙箱根目录；None / 空 = 解除限制。"""
    global _root
    with _lock:
        _root = os.path.realpath(path) if path else None


def get_sandbox_root() -> Optional[str]:
    with _lock:
        return _root


def check_write_allowed(target_path: str) -> Tuple[bool, str]:
    """目标路径是否允许写。未设 root → 总允许；设了 → 必须落在 root 内。

    返回 (ok, error_msg)。realpath 解析符号链接，防 symlink 逃逸。
    """
    root = get_sandbox_root()
    if not root:
        return True, ""
    try:
        rp = os.path.realpath(target_path)
    except Exception:
        rp = os.path.abspath(target_path)
    if rp == root or rp.startswith(root + os.sep):
        return True, ""
    return False, (
        f"[SANDBOX_DENIED] 拒绝写到工作文件夹之外：{target_path}\n"
        f"本次会话只允许在工作文件夹内读写：{root}\n"
        f"请把目标路径改到该文件夹内（用其下的绝对路径）后重试。"
    )
