"use strict";
/**
 * Claude CLI session management
 * Manages Claude CLI sessions within tmux for each worktree
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
exports.clearCachedClaudePath = clearCachedClaudePath;
exports.isSessionHealthy = isSessionHealthy;
exports.getSessionName = getSessionName;
exports.isClaudeInstalled = isClaudeInstalled;
exports.isClaudeRunning = isClaudeRunning;
exports.getClaudeSessionState = getClaudeSessionState;
exports.waitForPrompt = waitForPrompt;
exports.startClaudeSession = startClaudeSession;
exports.sendMessageToClaude = sendMessageToClaude;
exports.captureClaudeOutput = captureClaudeOutput;
exports.stopClaudeSession = stopClaudeSession;
exports.restartClaudeSession = restartClaudeSession;
const tmux_1 = require("../../lib/tmux/tmux");
const cli_patterns_1 = require("../../lib/detection/cli-patterns");
const session_key_sender_1 = require("../../lib/session-key-sender");
const child_process_1 = require("child_process");
const util_1 = require("util");
const promises_1 = require("fs/promises");
const logger_1 = require("../../lib/logger");
const logger = (0, logger_1.createLogger)('claude-session');
const execAsync = (0, util_1.promisify)(child_process_1.exec);
// ----- Helper Functions -----
/**
 * Extract error message from unknown error type
 * Provides consistent error message extraction across the module (DRY)
 *
 * @param error - Unknown error object
 * @returns Error message string
 */
function getErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
// ----- Shell Prompt Detection Constants -----
/**
 * Shell prompt ending characters for detecting shell-only tmux sessions
 * Extensible array to support multiple shell types (MF-002: OCP)
 * Placed in claude-session.ts as private constant (SF-S2-002: ISP - used only by isSessionHealthy())
 * - '$': bash/sh default prompt
 * - '%': zsh default prompt
 * - '#': root prompt (bash/zsh)
 *
 * C-S2-002: False positive risk assessment:
 * These characters are checked only at the END of trimmed output. This limits false
 * positives to cases where Claude CLI output ends with one of these characters
 * (e.g., output containing "$" at end of a code block). The risk is acceptable because:
 * (1) Claude CLI output typically ends with a prompt (❯) or thinking indicator, not shell chars
 * (2) A false positive triggers session recreation, which is a safe recovery action
 * (3) The check is combined with error pattern detection for multiple signals
 */
const SHELL_PROMPT_ENDINGS = ['$', '%', '#'];
// ----- Timeout and Polling Constants (OCP-001) -----
// These constants are exported to allow configuration and testing.
// Changing these values affects Claude CLI session startup behavior.
/**
 * Claude CLI initialization max wait time (milliseconds)
 *
 * This timeout allows sufficient time for Claude CLI to:
 * - Load and initialize its internal state
 * - Authenticate with Anthropic servers (if needed)
 * - Display the interactive prompt
 *
 * This timeout also covers trust dialog auto-response time (typically <1s).
 * When reducing this value, consider dialog response overhead.
 *
 * 15 seconds provides headroom for slower networks or cold starts.
 */
exports.CLAUDE_INIT_TIMEOUT = 15000;
/**
 * Initialization polling interval (milliseconds)
 *
 * How frequently we check if Claude CLI has finished initializing.
 * 300ms balances responsiveness with avoiding excessive polling overhead.
 */
exports.CLAUDE_INIT_POLL_INTERVAL = 300;
/**
 * Stability delay after prompt detection (milliseconds)
 *
 * This delay is necessary because Claude CLI renders its UI progressively:
 * 1. The prompt character (> or U+276F) appears first
 * 2. Additional UI elements (tips, suggestions) may render afterward
 * 3. Sending input too quickly can interrupt this rendering process
 *
 * The 500ms value was empirically determined to provide sufficient buffer
 * for Claude CLI to complete its initialization rendering while maintaining
 * responsive user experience. (DOC-001)
 *
 * @see Issue #152 - First message not being sent after session start
 */
exports.CLAUDE_POST_PROMPT_DELAY = 500;
/**
 * Prompt wait timeout before message send (milliseconds)
 *
 * When sending a message, we first verify Claude is at a prompt state.
 * This timeout limits how long we wait for Claude to return to prompt
 * if it's still processing a previous request.
 */
exports.CLAUDE_PROMPT_WAIT_TIMEOUT = 5000;
/**
 * Prompt wait timeout before message send (milliseconds).
 *
 * Used exclusively by sendMessageToClaude() to limit how long we wait
 * for Claude to return to a prompt state before sending a user message.
 * This is separate from CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms, the default
 * for waitForPrompt()) because sendMessageToClaude() may be called
 * shortly after session initialization, where Claude CLI needs additional
 * time to become ready.
 *
 * Relationship to other timeout constants:
 * - CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms): Default for waitForPrompt()
 * - CLAUDE_SEND_PROMPT_WAIT_TIMEOUT (10000ms): sendMessageToClaude() specific
 * - CLAUDE_INIT_TIMEOUT (15000ms): Session initialization timeout
 *
 * @see Issue #187 - Constant unification for sendMessageToClaude timeout
 */
exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = 10000;
/**
 * Prompt wait polling interval (milliseconds)
 *
 * How frequently we check for prompt state before sending messages.
 * 200ms provides quick response while minimizing CPU usage.
 */
exports.CLAUDE_PROMPT_POLL_INTERVAL = 200;
/**
 * Maximum expected length of a shell prompt line (characters)
 *
 * Shell prompts are typically under 40 characters (e.g., "user@host:~/project$" ~30 chars).
 * Lines at or above this threshold are not considered shell prompts, preventing
 * false positives from Claude CLI output that happens to end with $, %, or #.
 *
 * Used by isSessionHealthy() to distinguish shell prompts from CLI output.
 * 40 is an empirical threshold with safety margin.
 */
const MAX_SHELL_PROMPT_LENGTH = 40;
/**
 * Number of tail lines used for error pattern detection in isSessionHealthy()
 *
 * Error patterns are only searched within the last N lines of pane output,
 * not the entire buffer. This prevents false negatives where historical
 * (already recovered) errors in the scrollback trigger unhealthy detection.
 *
 * 10 lines provides sufficient window to catch recent errors while ignoring
 * historical ones that have scrolled up.
 */
const HEALTH_CHECK_ERROR_TAIL_LINES = 10;
/**
 * Cached Claude CLI path
 */
let cachedClaudePath = null;
/**
 * Clear cached Claude CLI path
 * Called when session start fails to allow path re-resolution
 * on next attempt (e.g., after CLI update or path change)
 * @internal Exported for testing purposes only.
 * Follows the same pattern as version-checker.ts resetCacheForTesting().
 * Function name clearCachedClaudePath() is retained (without ForTesting suffix)
 * because it is also called in production code (catch block), not only in tests.
 * (SF-S2-005: Consistent @internal usage with version-checker.ts precedent)
 */
function clearCachedClaudePath() {
    cachedClaudePath = null;
}
/**
 * Validate CLAUDE_PATH environment variable to prevent command injection
 * SEC-MF-001: OWASP A03:2021 - Injection prevention
 *
 * @param claudePath - Value from process.env.CLAUDE_PATH
 * @returns true if the path is safe to use
 */
function isValidClaudePath(claudePath) {
    // (1) Whitelist validation: only allow alphanumeric, path separators, dots, hyphens, underscores
    // SEC-MF-001: Rejects shell metacharacters (;, |, &, $, `, newlines, spaces in dangerous positions, etc.)
    const SAFE_PATH_PATTERN = /^[/a-zA-Z0-9._-]+$/;
    if (!SAFE_PATH_PATTERN.test(claudePath)) {
        logger.info('claudepath-contains-invalid-characters-i');
        return false;
    }
    // (2) Path traversal prevention: reject ../ sequences
    // SEC-MF-001: Prevents path traversal attacks
    if (claudePath.includes('..')) {
        logger.info('claudepath-contains-path');
        return false;
    }
    return true;
}
/**
 * Get Claude CLI path dynamically
 * Uses CLAUDE_PATH environment variable if set, otherwise finds via 'which'
 * SEC-MF-001: Validates CLAUDE_PATH before caching
 */
async function getClaudePath() {
    // Return cached path if available
    if (cachedClaudePath) {
        return cachedClaudePath;
    }
    // Check environment variable first with validation (SEC-MF-001)
    const envClaudePath = process.env.CLAUDE_PATH;
    if (envClaudePath) {
        if (isValidClaudePath(envClaudePath)) {
            try {
                await (0, promises_1.access)(envClaudePath, promises_1.constants.X_OK);
                cachedClaudePath = envClaudePath;
                return cachedClaudePath;
            }
            catch {
                logger.info('claudepath-is-not-executable:envclaudepa');
                // Fall through to fallback paths
            }
        }
        // If validation fails, ignore CLAUDE_PATH and proceed with fallback resolution
    }
    // Find claude via 'which' command
    try {
        const { stdout } = await execAsync('which claude', { timeout: 5000 });
        cachedClaudePath = stdout.trim();
        return cachedClaudePath;
    }
    catch {
        // Fallback to common paths
        const fallbackPaths = [
            '/opt/homebrew/bin/claude', // macOS Homebrew (Apple Silicon)
            '/usr/local/bin/claude', // macOS Homebrew (Intel) / Linux
            '/usr/bin/claude', // Linux system install
        ];
        for (const path of fallbackPaths) {
            try {
                await execAsync(`test -x "${path}"`, { timeout: 1000 });
                cachedClaudePath = path;
                return cachedClaudePath;
            }
            catch {
                // Path not found, try next
            }
        }
        throw new Error('Claude CLI not found. Set CLAUDE_PATH environment variable or install Claude CLI.');
    }
}
// ----- Common Helper Functions (SF-001) -----
/**
 * Capture tmux pane output and strip ANSI escape sequences
 * Consolidates the common capturePane + stripAnsi pattern (SF-001: DRY)
 *
 * @param sessionName - tmux session name
 * @param lines - Number of lines to capture (default: 50, captures from -lines)
 * @returns Clean pane output with ANSI codes removed
 */
async function getCleanPaneOutput(sessionName, lines = 50) {
    const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -lines });
    return (0, cli_patterns_1.stripAnsi)(output);
}
/**
 * Verify that Claude CLI is actually running inside a tmux session
 * Detects broken sessions where tmux exists but Claude failed to start
 *
 * Issue #405: Promoted from @internal to production export.
 * Used by worktrees/route.ts and worktrees/[id]/route.ts for
 * health-aware session status with listSessions() batch optimization.
 *
 * @param sessionName - tmux session name
 * @returns HealthCheckResult with healthy status and optional reason
 */
async function isSessionHealthy(sessionName) {
    try {
        // SF-001: Use shared helper instead of inline capturePane + stripAnsi
        const cleanOutput = await getCleanPaneOutput(sessionName);
        // MF-002: Check shell prompt endings from extensible array (OCP)
        const trimmed = cleanOutput.trim();
        // S2-F010: Empty output judgment (HealthCheckResult format)
        // C-S2-001: Empty output means tmux session exists but Claude CLI has no output.
        // This is treated as unhealthy because a properly running Claude CLI always
        // produces output (prompt, spinner, or response). An empty pane indicates
        // the CLI process has exited or failed to start.
        if (trimmed === '') {
            return { healthy: false, reason: 'empty output' };
        }
        // Active state detection: check for Claude prompt BEFORE error patterns.
        // This prevents false negatives where historical (recovered) errors in
        // the pane scrollback cause a currently-active session to be marked unhealthy.
        if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(trimmed)) {
            return { healthy: true };
        }
        // S2-F010: Error pattern detection - limited to tail lines only.
        // Only the last HEALTH_CHECK_ERROR_TAIL_LINES lines are searched, so
        // historical errors that have scrolled up do not trigger false negatives.
        const allLines = trimmed.split('\n').filter(line => line.trim() !== '');
        const tailLines = allLines.slice(-HEALTH_CHECK_ERROR_TAIL_LINES);
        const tailText = tailLines.join('\n');
        // MF-001: Check error patterns from cli-patterns.ts (SRP - pattern management centralized)
        for (const pattern of cli_patterns_1.CLAUDE_SESSION_ERROR_PATTERNS) {
            if (tailText.includes(pattern)) {
                return { healthy: false, reason: `error pattern: ${pattern}` };
            }
        }
        for (const regex of cli_patterns_1.CLAUDE_SESSION_ERROR_REGEX_PATTERNS) {
            if (regex.test(tailText)) {
                return { healthy: false, reason: `error pattern: ${regex.source}` };
            }
        }
        // S2-F002: Extract last line after empty line filtering
        const lastLine = allLines[allLines.length - 1]?.trim() ?? '';
        // F006: Line length check BEFORE SHELL_PROMPT_ENDINGS check (early return)
        if (lastLine.length >= MAX_SHELL_PROMPT_LENGTH) {
            // Long lines are not shell prompts -> treat as healthy (early return)
            return { healthy: true };
        }
        // F003: Individual pattern exclusions for SHELL_PROMPT_ENDINGS
        // NOTE(F003): If new false positive patterns are found in the future,
        // consider refactoring to a structure that associates exclusionPattern
        // with each SHELL_PROMPT_ENDINGS entry. Currently only % needs exclusion (YAGNI).
        if (SHELL_PROMPT_ENDINGS.some(ending => {
            if (!lastLine.endsWith(ending))
                return false;
            // Exclude N% pattern (e.g., "Context left until auto-compact: 7%")
            if (ending === '%' && /\d+%$/.test(lastLine))
                return false;
            return true;
        })) {
            return { healthy: false, reason: `shell prompt ending detected: ${lastLine}` };
        }
        return { healthy: true };
    }
    catch {
        // S3-F001: Catch block also returns HealthCheckResult format
        return { healthy: false, reason: 'capture error' };
    }
}
/**
 * Ensure the existing tmux session has a healthy Claude CLI process
 * If unhealthy, kill the session so it can be recreated
 * (SF-002: SRP - session health management separated from session creation)
 *
 * @param sessionName - tmux session name
 * @returns true if session is healthy and can be reused, false if it was killed
 */
async function ensureHealthySession(sessionName) {
    const result = await isSessionHealthy(sessionName);
    if (!result.healthy) {
        logger.warn('session-sessionname-unhealthy:resultreas');
        await (0, tmux_1.killSession)(sessionName);
        return false;
    }
    return true;
}
/**
 * Get tmux session name for a worktree
 *
 * @param worktreeId - Worktree ID
 * @returns tmux session name
 *
 * @example
 * ```typescript
 * getSessionName('feature-foo') // => 'mcbd-claude-feature-foo'
 * ```
 */
function getSessionName(worktreeId) {
    return `mcbd-claude-${worktreeId}`;
}
/**
 * Check if Claude is installed and available
 *
 * @returns True if Claude CLI is available
 */
async function isClaudeInstalled() {
    try {
        await execAsync('which claude', { timeout: 5000 });
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Check if Claude session is running
 * MF-S3-001: Includes health check to prevent reporting broken sessions as running.
 * Without this, API routes (especially send/route.ts) would skip startSession()
 * for broken sessions and attempt to send messages to a non-functional CLI.
 *
 * Performance: adds ~50ms overhead (capturePane + pattern match) per call.
 * This is acceptable given that API route response times are typically 100-500ms.
 *
 * @param worktreeId - Worktree ID
 * @returns True if Claude session exists AND Claude CLI is healthy
 *
 * @example
 * ```typescript
 * const running = await isClaudeRunning('feature-foo');
 * if (running) {
 *   console.log('Claude is ready');
 * }
 * ```
 */
async function isClaudeRunning(worktreeId) {
    const sessionName = getSessionName(worktreeId);
    const exists = await (0, tmux_1.hasSession)(sessionName);
    if (!exists) {
        return false;
    }
    // MF-S3-001: Verify session health to avoid reporting broken sessions as running
    // S2-F001: await + extract .healthy to maintain boolean return type
    const result = await isSessionHealthy(sessionName);
    if (!result.healthy) {
        logger.warn('session-sessionname-unhealthy:resultreas');
        return false;
    }
    return true;
}
/**
 * Get Claude session state
 *
 * C-S3-002: This function checks tmux session existence via hasSession() but
 * does NOT perform health checks (unlike isClaudeRunning()). This is intentional:
 * getClaudeSessionState() is a lightweight status query for UI display purposes,
 * while isClaudeRunning() performs the more expensive health check for operational
 * decisions (e.g., whether to recreate a session).
 *
 * If health-aware state is needed, callers should use isClaudeRunning() instead
 * or call ensureHealthySession() separately.
 *
 * @param worktreeId - Worktree ID
 * @returns Session state information (existence-based, not health-based)
 */
async function getClaudeSessionState(worktreeId) {
    const sessionName = getSessionName(worktreeId);
    const isRunning = await (0, tmux_1.hasSession)(sessionName);
    return {
        sessionName,
        isRunning,
        lastActivity: new Date(),
    };
}
/**
 * Wait for session to be at prompt state
 * Delegates to session-key-sender.ts (Issue #479: SRP split)
 *
 * @param sessionName - tmux session name
 * @param timeout - Timeout in milliseconds (default: CLAUDE_PROMPT_WAIT_TIMEOUT)
 * @throws {Error} If prompt is not detected within timeout
 *
 * @example
 * ```typescript
 * await waitForPrompt('mcbd-claude-feature-foo');
 * // Session is now ready to receive input
 * ```
 */
async function waitForPrompt(sessionName, timeout = exports.CLAUDE_PROMPT_WAIT_TIMEOUT) {
    return (0, session_key_sender_1.waitForPrompt)(sessionName, timeout);
}
/**
 * Start a Claude CLI session in tmux
 *
 * @param options - Session options
 * @throws {Error} If Claude CLI is not installed or session creation fails
 *
 * @example
 * ```typescript
 * await startClaudeSession({
 *   worktreeId: 'feature-foo',
 *   worktreePath: '/path/to/worktree',
 * });
 * ```
 */
async function startClaudeSession(options) {
    const { worktreeId, worktreePath } = options;
    // Check if Claude is installed
    const claudeAvailable = await isClaudeInstalled();
    if (!claudeAvailable) {
        throw new Error('Claude CLI is not installed or not in PATH');
    }
    const sessionName = getSessionName(worktreeId);
    // Check if session already exists
    const exists = await (0, tmux_1.hasSession)(sessionName);
    if (exists) {
        // SF-S2-004: Health check on existing session
        const healthy = await ensureHealthySession(sessionName);
        if (healthy) {
            logger.info('claude-session-sessionname');
            return;
        }
        // If not healthy, ensureHealthySession() already killed the session.
        // Fall through to the session creation logic below.
        // (SF-S2-004: Explicit fall-through instead of hidden re-entry)
    }
    try {
        // Create tmux session with large history buffer for Claude output
        await (0, tmux_1.createSession)({
            sessionName,
            workingDirectory: worktreePath,
            historyLimit: 50000,
        });
        // SF-S2-003: Sanitize environment after createSession, before launching Claude CLI
        await (0, session_key_sender_1.sanitizeSessionEnvironment)(sessionName);
        // Get Claude CLI path dynamically
        const claudePath = await getClaudePath();
        // Start Claude CLI in interactive mode using dynamically resolved path
        await (0, tmux_1.sendKeys)(sessionName, claudePath, true);
        // Wait for Claude to initialize with dynamic detection (OCP-001)
        // Use constants instead of hardcoded values
        const maxWaitTime = exports.CLAUDE_INIT_TIMEOUT;
        const pollInterval = exports.CLAUDE_INIT_POLL_INTERVAL;
        const startTime = Date.now();
        let initialized = false;
        let trustDialogHandled = false;
        while (Date.now() - startTime < maxWaitTime) {
            await new Promise((resolve) => setTimeout(resolve, pollInterval));
            try {
                // SF-001: Use getCleanPaneOutput helper (DRY)
                const cleanOutput = await getCleanPaneOutput(sessionName);
                // Claude is ready when we see the prompt (DRY-001)
                // Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts for consistency
                // Note: CLAUDE_SEPARATOR_PATTERN was removed from initialization check (Issue #187, P1-1)
                if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput)) {
                    // Wait for stability after prompt detection (CONS-007, DOC-001)
                    await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
                    logger.info('claude-initialized-in');
                    initialized = true;
                    break;
                }
                // Issue #201: Detect trust dialog and auto-respond with Enter
                // Condition order: CLAUDE_PROMPT_PATTERN (above) is checked first for shortest path
                if (!trustDialogHandled && cli_patterns_1.CLAUDE_TRUST_DIALOG_PATTERN.test(cleanOutput)) {
                    await (0, tmux_1.sendKeys)(sessionName, '', true);
                    trustDialogHandled = true;
                    logger.info('trust-dialog-detected');
                    // Continue polling to wait for prompt detection
                }
            }
            catch {
                // Ignore capture errors during initialization
            }
        }
        // Throw error on timeout instead of silently continuing (CONS-005, IMP-001)
        if (!initialized) {
            throw new Error(`Claude initialization timeout (${exports.CLAUDE_INIT_TIMEOUT}ms)`);
        }
        logger.info('started-claude-session:sessionname');
    }
    catch (error) {
        // MF-S2-002: Clear cached path on all failures (harmless for non-path failures)
        clearCachedClaudePath();
        // SEC-SF-002: Log detailed error server-side, throw generic message to client
        logger.error('session:start-failed', { error: error instanceof Error ? error.message : String(error) });
        throw new Error('Failed to start Claude session');
    }
}
/**
 * Send a message to Claude CLI
 * Delegates to session-key-sender.ts sendMessageToSession() (Issue #479: SRP split)
 *
 * @param worktreeId - Worktree ID
 * @param message - Message content to send
 * @throws {Error} If session doesn't exist
 *
 * @example
 * ```typescript
 * await sendMessageToClaude('feature-foo', 'Explain this code');
 * ```
 */
async function sendMessageToClaude(worktreeId, message) {
    const sessionName = getSessionName(worktreeId);
    await (0, session_key_sender_1.sendMessageToSession)(sessionName, message, exports.CLAUDE_POST_PROMPT_DELAY, exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT);
}
/**
 * Capture Claude session output
 *
 * @param worktreeId - Worktree ID
 * @param lines - Number of lines to capture (default: 1000)
 * @returns Captured output
 *
 * @example
 * ```typescript
 * const output = await captureClaudeOutput('feature-foo');
 * console.log(output);
 * ```
 */
async function captureClaudeOutput(worktreeId, lines = 1000) {
    const sessionName = getSessionName(worktreeId);
    // Check if session exists
    const exists = await (0, tmux_1.hasSession)(sessionName);
    if (!exists) {
        throw new Error(`Claude session ${sessionName} does not exist`);
    }
    try {
        return await (0, tmux_1.capturePane)(sessionName, { startLine: -lines });
    }
    catch (error) {
        throw new Error(`Failed to capture Claude output: ${getErrorMessage(error)}`);
    }
}
/**
 * Stop a Claude session
 * Delegates to session-key-sender.ts stopSession() (Issue #479: SRP split)
 *
 * @param worktreeId - Worktree ID
 * @returns True if session was stopped, false if it didn't exist
 *
 * @example
 * ```typescript
 * await stopClaudeSession('feature-foo');
 * ```
 */
async function stopClaudeSession(worktreeId) {
    const sessionName = getSessionName(worktreeId);
    return (0, session_key_sender_1.stopSession)(sessionName);
}
/**
 * Restart a Claude session
 *
 * @param options - Session options
 *
 * @example
 * ```typescript
 * await restartClaudeSession({
 *   worktreeId: 'feature-foo',
 *   worktreePath: '/path/to/worktree',
 * });
 * ```
 */
async function restartClaudeSession(options) {
    const { worktreeId } = options;
    // Stop existing session
    await stopClaudeSession(worktreeId);
    // Wait a moment before restarting
    await new Promise((resolve) => setTimeout(resolve, 1000));
    // Start new session
    await startClaudeSession(options);
}
