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/.planning/phases/01-core-infrastructure/01-03-PLAN.md
T

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
phase plan type wave depends_on files_modified autonomous requirements user_setup must_haves
01-core-infrastructure 03 execute 2
01-01
01-02
src/gsd_openclaw/tmux_session.py
tests/conftest.py
tests/test_tmux_session.py
true
SESS-01
SESS-02
SESS-03
SESS-07
truths artifacts key_links
User can create a new tmux session for a project
User can list all active gsd sessions with their project associations
User can destroy a session when project is complete
Session names follow gsd-{project} format with collision handling
Multiple sessions can run concurrently without interference
path provides exports min_lines
src/gsd_openclaw/tmux_session.py tmux session lifecycle management
TmuxSessionManager
TmuxSession
120
path provides contains
tests/test_tmux_session.py Test coverage for tmux operations test_create_session
from to via pattern
src/gsd_openclaw/tmux_session.py libtmux.Server server = libtmux.Server() libtmux.Server
from to via pattern
TmuxSessionManager state_manager.StateManager state persistence after operations StateManager
Implement tmux session lifecycle management using libtmux.

Purpose: Core functionality for creating, listing, and destroying tmux sessions. This is the primary interface to tmux that all other components will use.

Output: Working tmux_session.py with create/list/destroy operations using libtmux.

<execution_context> @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-core-infrastructure/01-CONTEXT.md @.planning/phases/01-core-infrastructure/01-RESEARCH.md

SessionState dataclass:

@dataclass
class SessionState:
    schema_version: str = "1.0"
    session_id: str = ""
    project_name: str = ""
    tmux_session_name: str = ""
    tmux_pane_id: str = ""
    created_at: str = ""
    last_active_at: str = ""
    command_count: int = 0
    last_output_snapshot: Optional[str] = None
    display_name: Optional[str] = None

StateManager class:

class StateManager:
    def save(self, state: SessionState) -> None: ...
    def load(self) -> Optional[SessionState]: ...

WorkspaceManager class:

class WorkspaceManager:
    def create(self) -> Workspace: ...
    def get_workspace(self) -> Workspace: ...

libtmux API:

import libtmux
server = libtmux.Server()

# Create session
session = server.new_session(
    session_name='gsd-my-project',
    attach=False,
    window_name='main'
)

# List sessions
for s in server.sessions:
    print(f"{s.name}: {s.session_id}")

# Find session
session = server.sessions.get(session_name='gsd-my-project')

# Kill session
session.kill()
Task 1: Implement TmuxSession dataclass and basic libtmux wrapper src/gsd_openclaw/tmux_session.py, tests/conftest.py, tests/test_tmux_session.py - Test 1: TmuxSession stores session metadata correctly - Test 2: libtmux Server connection works - Test 3: Server can list existing sessions Create TmuxSession dataclass and initial TmuxSessionManager:
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, List
import uuid
import libtmux

@dataclass
class TmuxSession:
    """Represents a managed tmux session."""
    session_id: str
    project_name: str
    tmux_session_name: str
    tmux_session: Optional[libtmux.Session] = None  # Runtime reference

    @property
    def exists(self) -> bool:
        if self.tmux_session is None:
            return False
        try:
            # Check if session still exists in tmux
            self.tmux_session.session_id
            return True
        except libtmux.exc.LibTmuxException:
            return False


class TmuxSessionManager:
    """Manages tmux session lifecycle using libtmux."""

    SESSION_PREFIX = "gsd-"

    def __init__(self, server: Optional[libtmux.Server] = None):
        self.server = server or libtmux.Server()

    def _generate_session_name(self, project_name: str) -> str:
        """Generate tmux session name from project name."""
        base_name = f"{self.SESSION_PREFIX}{project_name}"
        return base_name

    def _resolve_collision(self, base_name: str) -> str:
        """Add timestamp suffix if session name exists."""
        existing = self.server.sessions.filter(session_name=base_name)
        if not existing:
            return base_name
        suffix = datetime.now().strftime("-%Y%m%d-%H%M%S")
        return f"{base_name}{suffix}"

    def list_sessions(self) -> List[libtmux.Session]:
        """List all gsd-managed sessions."""
        return list(self.server.sessions.filter(
            session_name__startswith=self.SESSION_PREFIX
        ))

Add libtmux pytest fixtures to conftest.py for test isolation. pytest tests/test_tmux_session.py::test_server_connection -x pytest tests/test_tmux_session.py::test_list_sessions -x TmuxSession dataclass exists, TmuxSessionManager connects to libtmux, can list sessions

Task 2: Implement session creation with state persistence src/gsd_openclaw/tmux_session.py, tests/test_tmux_session.py - Test 1: create_session creates tmux session with correct name - Test 2: create_session persists state to workspace - Test 3: create_session handles name collision with timestamp suffix - Test 4: create_session creates workspace directory if missing - Test 5: create_session returns TmuxSession with all metadata Implement create_session method:
from gsd_openclaw.state_manager import StateManager, SessionState
from gsd_openclaw.workspace import WorkspaceManager

class TmuxSessionManager:
    # ... existing code ...

    def create_session(
        self,
        project_name: str,
        workspace_manager: Optional[WorkspaceManager] = None,
        display_name: Optional[str] = None
    ) -> TmuxSession:
        """Create a new tmux session for a project."""
        # Ensure workspace exists
        ws_manager = workspace_manager or WorkspaceManager(project_name)
        ws_manager.create()

        # Generate unique session name
        base_name = self._generate_session_name(project_name)
        tmux_name = self._resolve_collision(base_name)

        # Create tmux session (detached)
        tmux_session = self.server.new_session(
            session_name=tmux_name,
            attach=False,
            window_name='main'
        )

        # Create session state
        session_id = str(uuid.uuid4())[:8]
        now = datetime.now().isoformat()

        state = SessionState(
            session_id=session_id,
            project_name=project_name,
            tmux_session_name=tmux_name,
            tmux_pane_id=tmux_session.active_pane.pane_id,
            created_at=now,
            last_active_at=now,
            display_name=display_name
        )

        # Persist state
        state_manager = StateManager(project_name)
        state_manager.save(state)

        return TmuxSession(
            session_id=session_id,
            project_name=project_name,
            tmux_session_name=tmux_name,
            tmux_session=tmux_session
        )

This integrates with StateManager and WorkspaceManager from previous plans. pytest tests/test_tmux_session.py::test_create_session -x pytest tests/test_tmux_session.py::test_session_naming_collision -x create_session creates tmux session, persists state, handles name collisions

Task 3: Implement session destruction with cleanup src/gsd_openclaw/tmux_session.py, tests/test_tmux_session.py - Test 1: destroy_session kills tmux session - Test 2: destroy_session removes state file - Test 3: destroy_session handles already-destroyed session gracefully - Test 4: destroy_session with cleanup=True removes workspace Implement destroy_session method:
class TmuxSessionManager:
    # ... existing code ...

    def destroy_session(
        self,
        project_name: str,
        cleanup_workspace: bool = False
    ) -> bool:
        """Destroy a tmux session and optionally cleanup workspace."""
        state_manager = StateManager(project_name)
        state = state_manager.load()

        if state is None:
            return False  # No session to destroy

        # Kill tmux session
        try:
            tmux_session = self.server.sessions.get(
                session_name=state.tmux_session_name
            )
            if tmux_session:
                tmux_session.kill()
        except libtmux.exc.LibTmuxException:
            pass  # Session already gone

        # Remove state file
        if state_manager.state_path.exists():
            state_manager.state_path.unlink()
        if state_manager.state_path.with_suffix('.json.bak').exists():
            state_manager.state_path.with_suffix('.json.bak').unlink()

        # Optionally cleanup workspace
        if cleanup_workspace:
            workspace_manager = WorkspaceManager(project_name)
            workspace_manager.destroy()

        return True

The cleanup_workspace parameter allows keeping the workspace for later resumption if desired. pytest tests/test_tmux_session.py::test_destroy_session -x destroy_session kills tmux session, removes state, optionally cleans workspace

Task 4: Implement session listing with project info src/gsd_openclaw/tmux_session.py, tests/test_tmux_session.py - Test 1: list_gsd_sessions returns all gsd-* sessions - Test 2: list_gsd_sessions includes project associations from state files - Test 3: list_gsd_sessions handles orphan sessions (no state file) Implement list_gsd_sessions method that combines tmux session info with state:
from dataclasses import dataclass

@dataclass
class SessionInfo:
    """Session info for display."""
    tmux_name: str
    project_name: str
    session_id: str
    created_at: str
    last_active_at: str
    command_count: int
    display_name: Optional[str]
    is_orphan: bool = False  # tmux session exists but no state

class TmuxSessionManager:
    # ... existing code ...

    def list_gsd_sessions(self) -> List[SessionInfo]:
        """List all gsd sessions with project associations."""
        sessions_info = []

        # Get all gsd-* tmux sessions
        tmux_sessions = self.list_sessions()

        for tmux_session in tmux_sessions:
            tmux_name = tmux_session.name
            # Extract project name from tmux session name
            # Format: gsd-{project} or gsd-{project}-{timestamp}
            project_name = self._extract_project_name(tmux_name)

            # Try to load state
            state_manager = StateManager(project_name)
            state = state_manager.load()

            if state and state.tmux_session_name == tmux_name:
                # Matched session
                sessions_info.append(SessionInfo(
                    tmux_name=tmux_name,
                    project_name=project_name,
                    session_id=state.session_id,
                    created_at=state.created_at,
                    last_active_at=state.last_active_at,
                    command_count=state.command_count,
                    display_name=state.display_name,
                    is_orphan=False
                ))
            else:
                # Orphan session (no matching state)
                sessions_info.append(SessionInfo(
                    tmux_name=tmux_name,
                    project_name=project_name,
                    session_id="?",
                    created_at="?",
                    last_active_at="?",
                    command_count=0,
                    display_name=None,
                    is_orphan=True
                ))

        return sessions_info

    def _extract_project_name(self, tmux_name: str) -> str:
        """Extract project name from tmux session name."""
        # Remove prefix
        name = tmux_name[len(self.SESSION_PREFIX):]
        # Remove timestamp suffix if present (format: -YYYYMMDD-HHMMSS)
        import re
        match = re.match(r'^(.+?)(-\d{8}-\d{6})?$', name)
        return match.group(1) if match else name
pytest tests/test_tmux_session.py::test_list_gsd_sessions -x list_gsd_sessions returns session info with project associations, handles orphans Run full test suite: ```bash pytest tests/test_tmux_session.py -v ```

Verify integration with state_manager and workspace:

pytest tests/ -v -k "tmux or state or workspace"

<success_criteria>

  • TmuxSession dataclass exists with metadata
  • create_session creates tmux session with libtmux
  • create_session persists state and handles name collisions
  • destroy_session kills tmux session and removes state
  • list_gsd_sessions returns session info with project associations
  • Concurrent sessions work without interference
  • All tests pass: pytest tests/test_tmux_session.py -v </success_criteria>
After completion, create `.planning/phases/01-core-infrastructure/01-03-SUMMARY.md`