This repository has been archived on 2026-08-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw/skills/gsd-openclaw/src/gsd_openclaw/workspace.py
T

144 lines
4.3 KiB
Python

"""Workspace directory management for project isolation."""
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
import shutil
@dataclass
class Workspace:
"""Represents a project workspace with defined directory structure.
Each project has its own isolated workspace directory following the layout:
~/gsd-workspaces/{project}/
├── .gsd/
│ ├── state.json # Session state
│ ├── state.json.bak # State backup
│ └── session-dump/ # Session dump
├── code/ # Project code (optional)
└── io-logs/
└── {session-id}.log # I/O logs
"""
project_name: str
root: Optional[Path] = None
def __post_init__(self):
"""Compute root path if not provided."""
if self.root is None:
self.root = Path.home() / "gsd-workspaces" / self.project_name
elif isinstance(self.root, str):
self.root = Path(self.root)
@property
def gsd_dir(self) -> Path:
"""Path to .gsd directory containing state files."""
return self.root / ".gsd"
@property
def state_path(self) -> Path:
"""Path to state.json file."""
return self.gsd_dir / "state.json"
@property
def state_backup_path(self) -> Path:
"""Path to state.json.bak file."""
return self.gsd_dir / "state.json.bak"
@property
def session_dump_dir(self) -> Path:
"""Path to session-dump directory."""
return self.gsd_dir / "session-dump"
@property
def code_dir(self) -> Path:
"""Path to code directory."""
return self.root / "code"
@property
def io_logs_dir(self) -> Path:
"""Path to io-logs directory."""
return self.root / "io-logs"
def io_log_path(self, session_id: str) -> Path:
"""Get path to I/O log file for a specific session.
Args:
session_id: The session identifier.
Returns:
Path to the session's I/O log file.
"""
return self.io_logs_dir / f"{session_id}.log"
class WorkspaceManager:
"""Manages workspace creation, destruction, and lifecycle.
The WorkspaceManager handles:
- Creating workspace directory structure
- Destroying workspaces (cleanup)
- Checking workspace existence
- Providing workspace objects
"""
WORKSPACES_ROOT = Path.home() / "gsd-workspaces"
def __init__(self, project_name: str, root: Optional[Path] = None):
"""Initialize workspace manager.
Args:
project_name: Name of the project (used for workspace directory).
root: Optional custom root path for workspaces (useful for testing).
"""
self.project_name = project_name
self.root = root or self.WORKSPACES_ROOT
if isinstance(self.root, str):
self.root = Path(self.root)
self.workspace = Workspace(project_name, self.root / project_name)
def create(self) -> Workspace:
"""Create workspace directory structure.
Creates all required directories:
- .gsd/ (with session-dump/ subdirectory)
- code/
- io-logs/
This operation is idempotent - calling multiple times will not fail.
Returns:
The Workspace object with paths configured.
"""
# Create all directories
self.workspace.gsd_dir.mkdir(parents=True, exist_ok=True)
self.workspace.session_dump_dir.mkdir(exist_ok=True)
self.workspace.code_dir.mkdir(exist_ok=True)
self.workspace.io_logs_dir.mkdir(exist_ok=True)
return self.workspace
def destroy(self) -> None:
"""Remove entire workspace directory.
Completely removes the workspace and all its contents.
Safe to call even if workspace doesn't exist.
"""
if self.workspace.root.exists():
shutil.rmtree(self.workspace.root)
def exists(self) -> bool:
"""Check if workspace exists.
Returns:
True if workspace root directory exists, False otherwise.
"""
return self.workspace.root.exists()
def get_workspace(self) -> Workspace:
"""Get workspace object (does not create).
Returns:
The Workspace object with paths configured.
"""
return self.workspace