isolated_workspace.py 2.48 KB
"""Create an isolated, disposable copy of a workspace directory for a
Coder/Validator node -- or, in the Supervisor's blue-green deploy flow, for
a candidate instance -- to operate in.

isolate_workspace(source_dir) never modifies `source_dir`; it copies it
into a fresh temporary directory and returns the new path.

Adapted from graphloop's isolated_coding_workspace.py (2026-07-30). Only
change from the original: the default parent-dir constant and prefix now
follow graphlemon's own `.graphlemon/` runtime-directory convention instead
of graphloop's `.hermes/graphloop-isolated-workspaces`. Reconcile
DEFAULT_ISOLATION_PARENT_DIR against the final runtime-directory layout in
docs/sdd/05-实施约束与依赖.md when this is wired into a real Enabler unit.
"""
import os
import shutil
import tempfile
from typing import Optional

# Matches the graphlemon architecture doc's `.graphlemon/` convention for
# platform-owned runtime state. Kept as a module constant (not hardcoded at
# each call site) so a future SDD decision can change it in one place.
DEFAULT_ISOLATION_PARENT_DIR = os.path.expanduser("~/.graphlemon/isolated-workspaces")


class IsolatedWorkspaceError(Exception):
    """Raised when the source directory cannot be isolated."""


def isolate_workspace(source_dir: str, *, parent_dir: Optional[str] = None) -> str:
    """Copy `source_dir` into a fresh temporary directory and return its path.

    `parent_dir`, if given, is used as the parent of the fresh temp
    directory (created first if missing) instead of the OS default temp
    location. Some agent runtimes refuse to write under the OS default temp
    directory (e.g. a sandboxed CLI treating macOS's `/var/folders/.../T` as
    a sensitive system path and blocking writes to it even from a coding
    role) -- `parent_dir` lets callers that hit this route the isolated copy
    somewhere the worker/candidate-instance process can actually write to.
    Callers that don't pass `parent_dir` get plain `tempfile.mkdtemp()`
    behavior; callers that want the graphlemon-standard location should pass
    `DEFAULT_ISOLATION_PARENT_DIR` explicitly.
    """
    if not source_dir or not os.path.isdir(source_dir):
        raise IsolatedWorkspaceError(
            f"source_dir is not a directory: {source_dir!r}"
        )

    if parent_dir:
        os.makedirs(parent_dir, exist_ok=True)
    destination = tempfile.mkdtemp(prefix="graphlemon-isolated-", dir=parent_dir)
    shutil.copytree(source_dir, destination, dirs_exist_ok=True)
    return destination