Advertisement

← vibesdk

worker/services/sandbox/sandboxSdkClient.ts

89.6 KB Download file
import { getSandbox, Sandbox, parseSSEStream, LogEvent, ExecResult } from '@cloudflare/sandbox';

import {
    BootstrapResponse,
    GetInstanceResponse,
    BootstrapStatusResponse,
    ShutdownResponse,
    WriteFilesRequest,
    WriteFilesResponse,
    GetFilesResponse,
    ExecuteCommandsResponse,
    RuntimeErrorResponse,
    ClearErrorsResponse,
    StaticAnalysisResponse,
    DeploymentResult,
    FileTreeNode,
    RuntimeError,
    CommandExecutionResult,
    CodeIssue,
    InstanceDetails,
    LintSeverity,
    GetLogsResponse,
    ListInstancesResponse,
    StoredError,
    TemplateFile,
    InstanceCreationRequest,
} from './sandboxTypes';

import { createObjectLogger } from '../../logger';
import { env } from 'cloudflare:workers'
import { BaseSandboxService } from './BaseSandboxService';

import { 
    buildDeploymentConfig, 
    parseWranglerConfig, 
    deployToDispatch, 
    deployWorker,
} from '../deployer/deploy';
import { 
    createAssetManifest 
} from '../deployer/utils/index';
import { generateId } from '../../utils/idGenerator';
import { ResourceProvisioner } from './resourceProvisioner';
import { TemplateParser } from './templateParser';
import { ResourceProvisioningResult } from './types';
import { getPreviewDomain, resolvePreviewUrl } from '../../utils/urls';
import { isDev } from 'worker/utils/envs'
import { FileTreeBuilder } from './fileTreeBuilder';
import { DeploymentTarget } from 'worker/agents/core/types';
// Export the Sandbox class in your Worker
export { Sandbox as UserAppSandboxService } from "@cloudflare/sandbox";


interface InstanceMetadata {
    projectName: string;
    startTime: string;
    webhookUrl?: string;
    previewURL?: string;
    tunnelURL?: string;
    processId?: string;
    allocatedPort?: number;
    donttouch_files: string[];
    redacted_files: string[];
}

type SandboxType = Sandbox;

type ExecutionSession = Awaited<ReturnType<Sandbox['createSession']>>;

/**
 * Streaming event for enhanced command execution
 */
export interface StreamEvent {
    type: 'stdout' | 'stderr' | 'exit' | 'error';
    data?: string;
    code?: number;
    error?: string;
    timestamp: Date;
}

export enum AllocationStrategy {
    MANY_TO_ONE = 'many_to_one',
    ONE_TO_ONE = 'one_to_one',
}
  
function getAutoAllocatedSandbox(sessionId: string): string {
    // Distribute sessions across available containers using consistent hashing
    // Convert session ID to hash for deterministic assignment
    let hash = 0;
    for (let i = 0; i < sessionId.length; i++) {
      const char = sessionId.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    
    hash = Math.abs(hash);

    const max_instances = env.MAX_SANDBOX_INSTANCES ? Number(env.MAX_SANDBOX_INSTANCES) : 10;
    const containerIndex = hash % max_instances;
    const containerId = `container-pool-${containerIndex}`;
    
    console.log(`Session mapped to container`, { sessionId, containerId, hash, containerIndex });
    return containerId;
}

export class SandboxSdkClient extends BaseSandboxService {
    private sandbox: SandboxType;
    private metadataCache = new Map<string, InstanceMetadata>();
    private sessionCache = new Map<string, ExecutionSession>();

    constructor(sandboxId: string, agentId: string) {
        if (env.ALLOCATION_STRATEGY === AllocationStrategy.MANY_TO_ONE) {
            sandboxId = getAutoAllocatedSandbox(sandboxId);
        }
        super(sandboxId);
        this.sandbox = this.getSandbox();
        
        this.logger = createObjectLogger(this, 'SandboxSdkClient');
        this.logger.setFields({
            sandboxId: this.sandboxId,
            agentId,
        });
        this.logger.info('SandboxSdkClient initialized', { sandboxId: this.sandboxId });
    }

    async initialize(): Promise<void> {
        // Initialize default session for sandbox operations
        await this.getDefaultSession();
        
        // Run a echo command to check if the sandbox is working
        const echoResult = await this.safeSandboxExec('echo "Hello World"');
        if (echoResult.exitCode !== 0) {
            throw new Error(`Failed to run echo command: ${echoResult.stderr}`);
        }
        this.logger.info('Sandbox initialization complete');
    }

    private getWranglerKVKey(instanceId: string): string {
        return `wrangler-${instanceId}`;
    }

    private getSandbox(): SandboxType {
        if (!this.sandbox) {
            this.sandbox = getSandbox(env.Sandbox, this.sandboxId);
        }
        return this.sandbox;
    }

    /**
     * Generic session getter with caching and automatic recovery
     * Properly handles existing sessions and ensures correct cwd
     */
    private async getOrCreateSession(sessionId: string, cwd: string): Promise<ExecutionSession> {
        try {
            // Try to create a new session with the specified cwd
            this.logger.info('Creating new session', { sessionId, cwd });
            const session = await this.getSandbox().createSession({ id: sessionId, cwd });
            return session;
        } catch (error) {
            // If session already exists, get it
            this.logger.info('Session already exists, retrieving it', { sessionId, cwd });
            const existingSession = await this.getSandbox().getSession(sessionId);
            
            // Verify the cwd matches what we expect
            const pwdResult = await existingSession.exec('pwd');
            const actualCwd = pwdResult.stdout.trim();
            
            if (actualCwd !== cwd) {
                this.logger.warn('Existing session has wrong cwd, attempting to change directory', { 
                    sessionId, 
                    expectedCwd: cwd, 
                    actualCwd 
                });
                // Try to cd to the correct directory
                await existingSession.exec(`cd ${cwd}`);
                const verifyResult = await existingSession.exec('pwd');
                if (verifyResult.stdout.trim() !== cwd) {
                    // throw new Error(`Failed to set working directory to ${cwd}, currently at ${verifyResult.stdout.trim()}`);
                    this.logger.error(`Failed to set working directory to ${cwd}, currently at ${verifyResult.stdout.trim()}`);
                }
                this.logger.info('Successfully changed directory for existing session', { sessionId, cwd });
            }
            
            return existingSession;
        }
    }

    /**
     * Get or create a session for an instance with automatic caching.
     * Environment variables should be set via .dev.vars file.
     */
    private async getInstanceSession(instanceId: string, cwd?: string): Promise<ExecutionSession> {
        if (!this.sessionCache.has(instanceId)) {
            if (instanceId === 'sandbox-default') {
                cwd = '/workspace';
            } else {
                cwd = cwd || `/workspace/${instanceId}`;
            }
            const session = await this.getOrCreateSession(instanceId, cwd);
            this.sessionCache.set(instanceId, session);
        }
        const session = this.sessionCache.get(instanceId);
        return session!;
    }

    /**
     * Get or create default session for anonymous sandbox operations
     */
    private async getDefaultSession(): Promise<ExecutionSession> {
        return await this.getInstanceSession('sandbox-default', '/workspace');
    }

    private async executeCommand(instanceId: string, command: string, options?: {timeout?: number}): Promise<ExecResult> {
        const session = await this.getInstanceSession(instanceId);
        return await session.exec(command, options);
    }

    /**
     * Safe wrapper for direct sandbox exec calls using default session
     */
    private async safeSandboxExec(command: string, options?: {timeout?: number}): Promise<ExecResult> {
        const session = await this.getDefaultSession();
        return await session.exec(command, options);
    }

    /**
     * Invalidate session cache (call when instance is destroyed)
     */
    private invalidateSessionCache(instanceId: string): void {
        if (this.sessionCache.has(instanceId)) {
            this.sessionCache.delete(instanceId);
            this.logger.debug('Session cache invalidated', { instanceId });
        }
    }

    // /** Write a binary file to the sandbox using small base64 chunks to avoid large control messages. */
    // private async writeBinaryFileViaBase64(targetPath: string, data: ArrayBuffer, bytesPerChunk: number = 16 * 1024): Promise<void> {
    //     const dir = targetPath.includes('/') ? targetPath.slice(0, targetPath.lastIndexOf('/')) : '.';
    //     // Ensure directory and clean target file
    //     await this.safeSandboxExec(`mkdir -p '${dir}'`);
    //     await this.safeSandboxExec(`rm -f '${targetPath}'`);

    //     const buffer = new Uint8Array(data);
    //     for (let i = 0; i < buffer.length; i += bytesPerChunk) {
    //         const chunk = buffer.subarray(i, Math.min(i + bytesPerChunk, buffer.length));
    //         const base64Chunk = btoa(String.fromCharCode(...chunk));
    //         // Append decoded bytes into the target file inside the sandbox
    //         const appendResult = await this.safeSandboxExec(`printf '%s' '${base64Chunk}' | base64 -d >> '${targetPath}'`);
    //         if (appendResult.exitCode !== 0) {
    //             throw new Error(`Failed to append to ${targetPath}: ${appendResult.stderr}`);
    //         }
    //     }
    // }

    /**
     * Write multiple files efficiently using a single shell script
     * Reduces 2N requests to just 2 requests regardless of file count
     * Uses base64 encoding to handle all content safely
     */
    private async writeFilesViaScript(
        files: TemplateFile[],
        session: ExecutionSession
    ): Promise<Array<{file: string, success: boolean, error?: string}>> {
        if (files.length === 0) return [];

        this.logger.info('Writing files via shell script', { fileCount: files.length });

        // Generate shell script
        const scriptLines = ['#!/bin/bash'];
        
        for (const { filePath, fileContents } of files) {
            const utf8Bytes = new TextEncoder().encode(fileContents);
            
            // Convert bytes to base64 in chunks to avoid stack overflow
            const chunkSize = 8192;
            const base64Chunks: string[] = [];
            
            for (let i = 0; i < utf8Bytes.length; i += chunkSize) {
                const chunk = utf8Bytes.slice(i, i + chunkSize);
                // Convert chunk to binary string
                let binaryString = '';
                for (let j = 0; j < chunk.length; j++) {
                    binaryString += String.fromCharCode(chunk[j]);
                }
                // Encode chunk to base64
                base64Chunks.push(btoa(binaryString));
            }
            
            const base64 = base64Chunks.join('');
            
            scriptLines.push(
                `mkdir -p "$(dirname "${filePath}")" && echo '${base64}' | base64 -d > "${filePath}" && echo "OK:${filePath}" || echo "FAIL:${filePath}"`
            );
        }

        const script = scriptLines.join('\n');
        const scriptPath = '/tmp/batch_write.sh';

        try {
            // Write script (1 request)
            const writeResult = await session.writeFile(scriptPath, script);    // TODO: Checksum integrity verification
            if (!writeResult.success) {
                throw new Error('Failed to write batch script');
            }

            // Execute with bash
            const { stdout, stderr } = await session.exec(`bash ${scriptPath}`, { timeout: 60000 });
            
            // Parse results from output
            const output = stdout + stderr;
            const matches = output.matchAll(/OK:(.+)/g);
            const successPaths = new Set<string>();
            for (const match of matches) {
                if (match[1]) successPaths.add(match[1]);
            }
            
            const results = files.map(({ filePath }) => ({
                file: filePath,
                success: successPaths.has(filePath),
                error: successPaths.has(filePath) ? undefined : 'Write failed'
            }));

            const successCount = successPaths.size;
            const failedCount = files.length - successCount;

            if (failedCount > 0) {
                this.logger.warn('Batch write completed with errors', { 
                    total: files.length, 
                    success: successCount, 
                    failed: failedCount 
                });
            } else {
                this.logger.info('Batch write completed', { 
                    total: files.length, 
                    success: successCount 
                });
            }

            return results;

        } catch (error) {
            this.logger.error('Batch write failed', error);
            return files.map(({ filePath }) => ({
                file: filePath,
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
            }));
        }
    }

    async writeFilesBulk(instanceId: string, files: TemplateFile[]): Promise<WriteFilesResponse> {
        try {
            const session = await this.getInstanceSession(instanceId);
            // Use batch script for efficient writing (3 requests for any number of files)
            const filesToWrite = files.map(file => ({
                filePath: `/workspace/${instanceId}/${file.filePath}`,
                fileContents: file.fileContents
            }));
            
            const writeResults = await this.writeFilesViaScript(filesToWrite, session);
            
            // Map results back to original format
            const results: WriteFilesResponse['results'] = [];
            for (const writeResult of writeResults) {
                results.push({
                    file: writeResult.file.replace(`/workspace/${instanceId}/`, ''),
                    success: writeResult.success,
                    error: writeResult.error
                });
            }

            return {
                success: true,
                results,
                message: 'Files written successfully'
            };
        } catch (error) {
            this.logger.error('writeFiles', error, { instanceId });
            return {
                success: false,
                results: files.map(f => ({ file: f.filePath, success: false, error: 'Instance error' })),
                message: 'Failed to write files'
            };
        }
    }

    async updateProjectName(instanceId: string, projectName: string): Promise<boolean> {
        try {
            await this.updateProjectConfiguration(instanceId, projectName);
            try {
                const metadata = await this.getInstanceMetadata(instanceId);
                const updated = { ...metadata, projectName } as InstanceMetadata;
                await this.storeInstanceMetadata(instanceId, updated);
            } catch (error) {
                this.logger.error('Failed to update instance metadata', error);
            }
            return true;
        } catch (error) {
            this.logger.error('Failed to update project name', error);
            return false;
        }
    }

    private getInstanceMetadataFile(instanceId: string): string {
        return `/workspace/${instanceId}-metadata.json`;
    }

    private async getInstanceMetadata(instanceId: string): Promise<InstanceMetadata> {
        // Check cache first
        if (this.metadataCache.has(instanceId)) {
            return this.metadataCache.get(instanceId)!;
        }
        
        // Cache miss - read from disk
        try {
            const session = await this.getDefaultSession();
            const metadataFile = await session.readFile(this.getInstanceMetadataFile(instanceId));
            if (!metadataFile.success) {
                throw new Error('Failed to read instance metadata file');
            }
            const metadata = JSON.parse(metadataFile.content) as InstanceMetadata;
            this.metadataCache.set(instanceId, metadata); // Cache it
            return metadata;
        } catch (error) {
            this.logger.error(`Failed to read instance metadata: ${error instanceof Error ? error.message : 'Unknown error'}`);
            throw new Error(`Failed to read instance metadata: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }

    private async storeInstanceMetadata(instanceId: string, metadata: InstanceMetadata): Promise<void> {
        const session = await this.getDefaultSession();
        const result = await session.writeFile(this.getInstanceMetadataFile(instanceId), JSON.stringify(metadata));
        if (!result.success) {
            throw new Error(`Failed to write instance metadata: ${result.path}`);
        }
        this.metadataCache.set(instanceId, metadata); // Update cache
    }

    private invalidateMetadataCache(instanceId: string): void {
        this.metadataCache.delete(instanceId);
    }

    private async allocateAvailablePort(excludedPorts: number[] = [3000]): Promise<number> {
        const startTime = Date.now();
        const excludeList = excludedPorts.join(' ');
        
        // Single command to find first available port in dev range (8001-8999)
        const findPortCmd = `
            for port in $(seq 8001 8999); do
                if ! echo "${excludeList}" | grep -q "\\\\b$port\\\\b" && 
                   ! netstat -tuln 2>/dev/null | grep -q ":$port " && 
                   ! ss -tuln 2>/dev/null | grep -q ":$port "; then
                    echo $port
                    break
                fi
            done
        `;
        
        const result = await this.safeSandboxExec(findPortCmd.trim());
        const endTime = Date.now();
        const duration = (endTime - startTime) / 1000;
        this.logger.info(`Port allocation took ${duration} seconds`);
        
        const portStr = result.stdout.trim();
        if (portStr) {
            const port = parseInt(portStr);
            this.logger.info(`Allocated available port: ${port}`);
            return port;
        }
        
        throw new Error('No available ports found in range 8001-8999');
    }
    
    private async buildFileTree(instanceId: string): Promise<FileTreeNode | undefined> {
        try {
            // Generate find command with exclusions
            const { dirExclusions, fileExclusions } = FileTreeBuilder.generateFindExclusions();
            
            // Build the command dynamically
            const buildTreeCmd = `echo "===FILES==="; find . -type d \\( ${dirExclusions} \\) -prune -o \\( -type f ${fileExclusions} \\) -print; echo "===DIRS==="; find . -type d \\( ${dirExclusions} \\) -prune -o -type d -print`;

            const filesResult = await this.executeCommand(instanceId, buildTreeCmd);
            if (filesResult.exitCode === 0) {
                return FileTreeBuilder.buildFromFindOutput(filesResult.stdout.trim());
            }
        } catch (error) {
            this.logger.warn('Failed to build file tree', error);
        }
        return undefined;
    }

    // ==========================================
    // INSTANCE LIFECYCLE
    // ==========================================

    async listAllInstances(): Promise<ListInstancesResponse> {
        try {
            this.logger.info('Retrieving instance metadata');
            
            // Use a single command to find metadata files only in current directory (not nested)
            const bulkResult = await this.safeSandboxExec(`find . -maxdepth 1 -name "*-metadata.json" -type f -exec sh -c 'echo "===FILE:$1==="; cat "$1"' _ {} \\;`);
            
            if (bulkResult.exitCode !== 0) {
                return {
                    success: true,
                    instances: [],
                    count: 0
                };
            }
            
            const instances: InstanceDetails[] = [];
            
            // Parse the combined output
            const metadataSections: string[] = bulkResult.stdout.split('===FILE:').filter((section: string) => section.trim());
            
            for (const section of metadataSections) {
                try {
                    const lines = section.trim().split('\n');
                    if (lines.length < 2) continue;
                    
                    // First line contains the file path, remaining lines contain the JSON
                    const filePath = lines[0].replace('===', '');
                    const jsonContent = lines.slice(1).join('\n');
                    
                    // Extract instance ID from filename (remove ./ prefix and -metadata.json suffix)
                    const instanceId = filePath.replace('./', '').replace('-metadata.json', '');
                    
                    // Parse metadata
                    const metadata = JSON.parse(jsonContent) as InstanceMetadata;
                    
                    // Update cache with the metadata we just read
                    this.metadataCache.set(instanceId, metadata);
                    
                    // Create lightweight instance details from metadata
                    const instanceDetails: InstanceDetails = {
                        runId: instanceId,
                        startTime: new Date(metadata.startTime),
                        uptime: Math.floor((Date.now() - new Date(metadata.startTime).getTime()) / 1000),
                        directory: instanceId,
                        serviceDirectory: instanceId,
                        previewURL: resolvePreviewUrl(metadata.previewURL, metadata.tunnelURL, env),
                        processId: metadata.processId,
                        tunnelURL: metadata.tunnelURL,
                        // Skip file tree
                        fileTree: undefined,
                        runtimeErrors: undefined
                    };
                    
                    instances.push(instanceDetails);
                } catch (error) {
                    this.logger.warn(`Failed to process metadata section`, error);
                }
            }
            
            this.logger.info('Instance list retrieved', { instanceCount: instances.length });
            
            return {
                success: true,
                instances,
                count: instances.length
            };
        } catch (error) {
            this.logger.error('listAllInstances', error);
            return {
                success: false,
                instances: [],
                count: 0,
                error: `Failed to list instances: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    /**
     * Waits for the development server to be ready by monitoring logs for readiness indicators
     */
    private async waitForServerReady(instanceId: string, processId: string, maxWaitTimeMs: number = 10000): Promise<boolean> {
        const startTime = Date.now();
        const pollIntervalMs = 500;
        const maxAttempts = Math.ceil(maxWaitTimeMs / pollIntervalMs);
        
        // Patterns that indicate the server is ready
        const readinessPatterns = [
            /http:\/\/[^\s]+/,           // Any HTTP URL (most reliable)
            /ready in \d+/i,             // Vite "ready in X ms"
            /Local:\s+http/i,            // Vite local server line
            /Network:\s+http/i,          // Vite network server line
            /server running/i,           // Generic server running message
            /listening on/i              // Generic listening message
        ];

        this.logger.info('Waiting for development server', { instanceId, processId, timeoutMs: maxWaitTimeMs });

        for (let attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                // Get recent logs only to avoid processing old content
                const logsResult = await this.getLogs(instanceId, true);
                
                if (logsResult.success && logsResult.logs.stdout) {
                    const logs = logsResult.logs.stdout;
                    
                    // Check for any readiness pattern
                    for (const pattern of readinessPatterns) {
                        if (pattern.test(logs)) {
                            const elapsedTime = Date.now() - startTime;
                            this.logger.info('Development server ready', { instanceId, elapsedTimeMs: elapsedTime, attempts: `${attempt}/${maxAttempts}` });
                            return true;
                        }
                    }
                }
                
                // Wait before next attempt (except on last attempt)
                if (attempt < maxAttempts) {
                    await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
                }
                
            } catch (error) {
                this.logger.warn(`Error checking server readiness for ${instanceId} (attempt ${attempt}):`, error);
                // Continue trying even if there's an error getting logs
                if (attempt < maxAttempts) {
                    await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
                }
            }
        }
        
        const elapsedTime = Date.now() - startTime;
        this.logger.warn('Development server readiness timeout', { instanceId, elapsedTimeMs: elapsedTime, totalAttempts: maxAttempts });
        return false;
    }

    private async startDevServer(instanceId: string, initCommand: string, port: number): Promise<string> {
        try {
            // Use session-based process management
            // Note: Environment variables should already be set via setLocalEnvVars
            const session = await this.getOrCreateSession(`${instanceId}-dev`, `/workspace/${instanceId}`);
            
            // Start process with env vars inline for those not in .dev.vars
            const process = await session.startProcess(
                `VITE_LOGGER_TYPE=json PORT=${port} monitor-cli process start --instance-id ${instanceId} --port ${port} -- ${initCommand}`
            );
            this.logger.info('Development server started', { instanceId, processId: process.id });
            
            // Wait for the server to be ready (non-blocking - always returns the process ID)
            try {
                const isReady = await this.waitForServerReady(instanceId, process.id, 10000);
                if (isReady) {
                    this.logger.info('Development server is ready', { instanceId });
                } else {
                    this.logger.warn('Development server may not be fully ready', { instanceId });
                }
            } catch (readinessError) {
                this.logger.warn(`Error during readiness check for ${instanceId}:`, readinessError);
                this.logger.info('Continuing with server startup despite readiness check error', { instanceId });
            }
            
            return process.id;
        } catch (error) {
            this.logger.warn('Failed to start dev server', error);
            throw error;
        }
    }

    /**
     * Provisions Cloudflare resources for template placeholders in wrangler.jsonc
     */
    private async provisionTemplateResources(instanceId: string, projectName: string): Promise<ResourceProvisioningResult> {
        try {
            const session = await this.getInstanceSession(instanceId);
            
            // Read wrangler.jsonc file using absolute path
            const wranglerFile = await session.readFile(`/workspace/${instanceId}/wrangler.jsonc`);
            if (!wranglerFile.success) {
                this.logger.info(`No wrangler.jsonc found for ${instanceId}, skipping resource provisioning`);
                return {
                    success: true,
                    provisioned: [],
                    failed: [],
                    replacements: {},
                    wranglerUpdated: false
                };
            }

            // Parse and detect placeholders
            const templateParser = new TemplateParser(this.logger);
            const parseResult = templateParser.parseWranglerConfig(wranglerFile.content);

            if (!parseResult.hasPlaceholders) {
                this.logger.info('No placeholders found in wrangler configuration', { instanceId });
                return {
                    success: true,
                    provisioned: [],
                    failed: [],
                    replacements: {},
                    wranglerUpdated: false
                };
            }

            this.logger.info('Placeholders found for provisioning', { instanceId, count: parseResult.placeholders.length });

            // Initialize resource provisioner (skip if credentials are not available)
            let resourceProvisioner: ResourceProvisioner;
            try {
                resourceProvisioner = new ResourceProvisioner(this.logger);
            } catch (error) {
                this.logger.warn(`Cannot initialize resource provisioner: ${error instanceof Error ? error.message : 'Unknown error'}`);
                return {
                    success: true,
                    provisioned: [],
                    failed: parseResult.placeholders.map(p => ({
                        placeholder: p.placeholder,
                        resourceType: p.resourceType,
                        error: 'Missing Cloudflare credentials',
                        binding: p.binding
                    })),
                    replacements: {},
                    wranglerUpdated: false
                };
            }
            
            const provisioned: ResourceProvisioningResult['provisioned'] = [];
            const failed: ResourceProvisioningResult['failed'] = [];
            const replacements: Record<string, string> = {};

            // Provision each resource
            for (const placeholderInfo of parseResult.placeholders) {
                this.logger.info(`Provisioning ${placeholderInfo.resourceType} resource for placeholder ${placeholderInfo.placeholder}`);
                
                const provisionResult = await resourceProvisioner.provisionResource(
                    placeholderInfo.resourceType,
                    projectName
                );

                if (provisionResult.success && provisionResult.resourceId) {
                    provisioned.push({
                        placeholder: placeholderInfo.placeholder,
                        resourceType: placeholderInfo.resourceType,
                        resourceId: provisionResult.resourceId,
                        binding: placeholderInfo.binding
                    });
                    replacements[placeholderInfo.placeholder] = provisionResult.resourceId;
                } else {
                    failed.push({
                        placeholder: placeholderInfo.placeholder,
                        resourceType: placeholderInfo.resourceType,
                        error: provisionResult.error || 'Unknown error',
                        binding: placeholderInfo.binding
                    });
                    this.logger.warn(`Failed to provision ${placeholderInfo.resourceType} for ${placeholderInfo.placeholder}: ${provisionResult.error}`);
                }
            }

            // Update wrangler.jsonc if we have replacements
            let wranglerUpdated = false;
            if (Object.keys(replacements).length > 0) {
                const updatedContent = templateParser.replacePlaceholders(wranglerFile.content, replacements);
                const writeResult = await session.writeFile(`/workspace/${instanceId}/wrangler.jsonc`, updatedContent);
                
                if (writeResult.success) {
                    wranglerUpdated = true;
                    this.logger.info(`Updated wrangler.jsonc with ${Object.keys(replacements).length} resource IDs for ${instanceId}`);
                    this.logger.info(templateParser.createReplacementSummary(replacements));
                } else {
                    this.logger.error(`Failed to update wrangler.jsonc for ${instanceId}`);
                }
            }

            const result: ResourceProvisioningResult = {
                success: failed.length === 0,
                provisioned,
                failed,
                replacements,
                wranglerUpdated
            };

            if (failed.length > 0) {
                this.logger.warn(`Resource provisioning completed with ${failed.length} failures for ${instanceId}`);
            } else {
                this.logger.info(`Resource provisioning completed successfully for ${instanceId}`);
            }

            return result;
        } catch (error) {
            this.logger.error(`Exception during resource provisioning for ${instanceId}:`, error);
            return {
                success: false,
                provisioned: [],
                failed: [],
                replacements: {},
                wranglerUpdated: false
            };
        }
    }

    /*
    * Starts a cloudflared tunnel for the specified instance
    * Super usefulfor local development
    */
    private async startCloudflaredTunnel(instanceId: string, port: number): Promise<string> {
        try {
            const session = await this.getOrCreateSession(`${instanceId}-tunnel`, `/workspace/${instanceId}`);
            const process = await session.startProcess(
                `cloudflared tunnel --url http://localhost:${port}`
            );
            this.logger.info(`Started cloudflared tunnel for ${instanceId}`);

            // Stream process logs to extract the preview URL
            const logStream = await this.getSandbox().streamProcessLogs(process.id);
            
            return new Promise<string>((resolve, reject) => {
                const timeout = setTimeout(() => {
                    // reject(new Error('Timeout waiting for cloudflared tunnel URL'));
                    this.logger.warn('Timeout waiting for cloudflared tunnel URL');
                    resolve('');
                }, 20000); // 20 second timeout

                const processLogs = async () => {
                    try {
                        for await (const event of parseSSEStream<LogEvent>(logStream)) {
                            if (event.data) {
                                const logLine = event.data;
                                this.logger.info(`Cloudflared log ===> ${logLine}`);
                                
                                // Look for the preview URL in the logs
                                // Format: https://subdomain.trycloudflare.com
                                const urlMatch = logLine.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
                                if (urlMatch) {
                                    clearTimeout(timeout);
                                    const previewURL = urlMatch[0];
                                    this.logger.info(`Found cloudflared tunnel URL: ${previewURL}`);
                                    resolve(previewURL);
                                    return;
                                }
                            }
                        }
                    } catch (error) {
                        this.logger.error('Cloudflare tunnel process failed', error);
                        clearTimeout(timeout);
                        reject(error);
                    }
                };

                processLogs();
            });
        } catch (error) {
            this.logger.warn('Failed to start cloudflared tunnel', error);
            throw error;
        }
    }

    /**
     * Updates project configuration files with the specified project name
     */
    private async updateProjectConfiguration(instanceId: string, projectName: string): Promise<void> {
        try {
            const session = await this.getInstanceSession(instanceId);
            
            // Update package.json with new project name (top-level only)
            this.logger.info(`Updating package.json with project name: ${projectName}`);
            const packageJsonResult = await session.exec(`sed -i '1,10s/^[ \t]*"name"[ ]*:[ ]*"[^"]*"/  "name": "${projectName}"/' package.json`);
            
            if (packageJsonResult.exitCode !== 0) {
                this.logger.warn('Failed to update package.json', packageJsonResult.stderr);
            }
            
            // Update wrangler.jsonc with new project name (top-level only)
            this.logger.info(`Updating wrangler.jsonc with project name: ${projectName}`);
            const wranglerResult = await session.exec(`sed -i '0,/"name":/s/"name"[ ]*:[ ]*"[^"]*"/"name": "${projectName}"/' wrangler.jsonc`);
               
            if (wranglerResult.exitCode !== 0) {
                this.logger.warn('Failed to update wrangler.jsonc', wranglerResult.stderr);
            }
            
            this.logger.info('Project configuration updated successfully');
        } catch (error) {
            this.logger.error(`Error updating project configuration: ${error}`);
            throw error;
        }
    }

    private async setLocalEnvVars(instanceId: string, localEnvVars: Record<string, string>): Promise<void> {
        try {
            // Write .dev.vars file - tools will read environment variables from this file
            const session = await this.getInstanceSession(instanceId);
            const envVarsContent = Object.entries(localEnvVars)
                .map(([key, value]) => `${key}=${value}`)
                .join('\n');
            const result = await session.writeFile(`/workspace/${instanceId}/.dev.vars`, envVarsContent);
            if (!result.success) {
                throw new Error('Failed to write .dev.vars file');
            }
            this.logger.info('Environment variables written to .dev.vars', { instanceId, varCount: Object.keys(localEnvVars).length });
        } catch (error) {
            this.logger.error(`Error setting local environment variables: ${error}`);
            throw error;
        }
    }

    private async setupInstance(
        instanceId: string,
        projectName: string,
        initCommand: string,
        localEnvVars?: Record<string, string>,
    ): Promise<{previewURL: string, tunnelURL: string, processId: string, allocatedPort: number} | undefined> {
        try {
            const sandbox = this.getSandbox();
            // Update project configuration with the specified project name
            await this.updateProjectConfiguration(instanceId, projectName);
            
            // Provision Cloudflare resources if template has placeholders
            const resourceProvisioningResult = await this.provisionTemplateResources(instanceId, projectName);
            if (!resourceProvisioningResult.success && resourceProvisioningResult.failed.length > 0) {
                this.logger.warn(`Some resources failed to provision for ${instanceId}, but continuing setup process`);
            }
            
            // Store wrangler.jsonc configuration in KV after resource provisioning
            try {
                const session = await this.getInstanceSession(instanceId);
                const wranglerConfigFile = await session.readFile(`/workspace/${instanceId}/wrangler.jsonc`);
                if (wranglerConfigFile.success) {
                    await env.VibecoderStore.put(this.getWranglerKVKey(instanceId), wranglerConfigFile.content);
                    this.logger.info('Wrangler configuration stored in KV', { instanceId });
                } else {
                    this.logger.warn('Could not read wrangler.jsonc for KV storage', { instanceId });
                }
            } catch (error) {
                this.logger.warn('Failed to store wrangler config in KV', { instanceId, error: error instanceof Error ? error.message : 'Unknown error' });
                // Non-blocking - continue with setup
            }
            // If on local development, start cloudflared tunnel
            let tunnelUrlPromise = Promise.resolve('');
            // Allocate single port for both dev server and tunnel
            const allocatedPort = await this.allocateAvailablePort();

            if (isDev(env) || env.USE_TUNNEL_FOR_PREVIEW) {
                this.logger.info('Starting cloudflared tunnel for local development', { instanceId });
                tunnelUrlPromise = this.startCloudflaredTunnel(instanceId, allocatedPort);
            }

            this.logger.info('Installing dependencies', { instanceId });
            const [installResult, tunnelURL] = await Promise.all([
                this.executeCommand(instanceId, `bun install`, { timeout: 40000 }),
                tunnelUrlPromise
            ]);
            this.logger.info('Dependencies installed', { instanceId, tunnelURL });
                
            if (installResult.exitCode === 0) {
                // Try to start development server in background
                try {
                    if (localEnvVars) {
                        await this.setLocalEnvVars(instanceId, localEnvVars);
                    }
                    // Start dev server on allocated port
                    const processId = await this.startDevServer(instanceId, initCommand, allocatedPort);
                    this.logger.info('Instance created successfully', { instanceId, processId, port: allocatedPort });
                        
                    // Expose the same port for preview URL
                    const previewResult = await sandbox.exposePort(allocatedPort, { hostname: getPreviewDomain(env) });
                    let previewURL = previewResult.url;
                    if (!isDev(env)) {
                        const previewDomain = getPreviewDomain(env);
                        if (previewDomain) {
                            // Replace CUSTOM_DOMAIN with previewDomain in previewURL
                            previewURL = previewURL.replace(env.CUSTOM_DOMAIN, previewDomain);
                        }
                    }

                    this.logger.info('Preview URL exposed', { instanceId, previewURL, tunnelURL });
                        
                    return { previewURL, tunnelURL, processId, allocatedPort };
                } catch (error) {
                    this.logger.warn('Failed to start dev server', error);
                    return undefined;
                }
            } else {
                this.logger.warn('Failed to install dependencies', installResult.stderr);
            }
        } catch (error) {
            this.logger.warn('Failed to setup instance', error);
        }
        
        return undefined;
    }
    
    async createInstance(
        options: InstanceCreationRequest
    ): Promise<BootstrapResponse> {
        const { files, projectName, webhookUrl, envVars, initCommand } = options;   
        try {
            // Environment variables will be set via session creation on first use
            if (envVars && Object.keys(envVars).length > 0) {
                this.logger.info('Environment variables will be configured via session', { envVars: Object.keys(envVars) });
            }
            let instanceId: string;
            if (env.ALLOCATION_STRATEGY === 'one_to_one') {
                // Multiple instances shouldn't exist in the same sandbox

                // If there are already instances running in sandbox, log them
                const instancesResp = await this.listAllInstances();
                if (instancesResp.success && instancesResp.instances.length > 0) {
                    this.logger.error('There are already instances running in sandbox, creating a new instance may cause issues', { instances: instancesResp.instances });
                    // Try to see if this instance actually exists and if the process is active
                    const firstInstance = instancesResp.instances[0];
                    const instanceStatus = await this.getInstanceStatus(firstInstance.runId);
                    if (instanceStatus.success && instanceStatus.isHealthy) {
                        this.logger.error('Instance already exists and is active, creating a new instance may cause issues', { instance: firstInstance });
                        // Return instance information
                        return {
                            success: true,
                            runId: firstInstance.runId,
                            previewURL: instanceStatus.previewURL,
                            tunnelURL: instanceStatus.tunnelURL,
                            processId: instanceStatus.processId,
                            message: instanceStatus.message
                        };
                    } else {
                        this.logger.error('Instance already exists but is not active, Removing old instance', { instance: firstInstance });
                        await this.shutdownInstance(firstInstance.runId);
                    }
                }
            
                instanceId = `i-${this.sandboxId}`;
            } else {
                instanceId = `i-${generateId()}`;
            }
            this.logger.info('Creating sandbox instance', { instanceId, projectName });

            const dontTouchFile = files.find(f => f.filePath === '.donttouch_files.json');
            const dontTouchFiles = dontTouchFile ? JSON.parse(dontTouchFile.fileContents) : [];
            
            const redactedFile = files.find(f => f.filePath === '.redacted_files.json');
            const redactedFiles = redactedFile ? JSON.parse(redactedFile.fileContents) : [];

            // Create directory for instance
            await this.sandbox.exec(`mkdir -p /workspace/${instanceId}`);

            // Write files in bulk to sandbox
            const rawResults = await this.writeFilesBulk(instanceId, files);
            if (!rawResults.success) {
                return {
                    success: false,
                    error: 'Failed to write files to sandbox'
                };
            }
            
            const results = await this.setupInstance(instanceId, projectName, initCommand, envVars);
            if (!results) {
                return {
                    success: false,
                    error: 'Failed to setup instance'
                };
            }
            // Store instance metadata
            const metadata = {
                projectName: projectName,
                startTime: new Date().toISOString(),
                webhookUrl: webhookUrl,
                previewURL: results.previewURL,
                processId: results.processId,
                tunnelURL: results.tunnelURL,
                allocatedPort: results.allocatedPort,
                donttouch_files: dontTouchFiles,
                redacted_files: redactedFiles,
            };
            await this.storeInstanceMetadata(instanceId, metadata);

            return {
                success: true,
                runId: instanceId,
                message: `Successfully created instance ${instanceId}`,
                previewURL: results.previewURL,
                tunnelURL: results.tunnelURL,
                processId: results.processId,
            };
        } catch (error) {
            this.logger.error(`Failed to create instance for project ${projectName}`, error);
            return {
                success: false,
                error: `Failed to create instance: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    async getInstanceDetails(instanceId: string): Promise<GetInstanceResponse> {
        try {            
            // Get instance metadata
            const metadata = await this.getInstanceMetadata(instanceId);
            if (!metadata) {
                return {
                    success: false,
                    error: `Instance ${instanceId} not found or metadata corrupted`
                };
            }

            const startTime = new Date(metadata.startTime);
            const uptime = Math.floor((Date.now() - startTime.getTime()) / 1000);

            // Get runtime errors
            const [fileTree, runtimeErrors] = await Promise.all([
                this.buildFileTree(instanceId),
                this.getInstanceErrors(instanceId)
            ]);

            const instanceDetails: InstanceDetails = {
                runId: instanceId,
                startTime,
                uptime,
                directory: instanceId,
                serviceDirectory: instanceId,
                fileTree,
                runtimeErrors: runtimeErrors.errors,
                previewURL: resolvePreviewUrl(metadata.previewURL, metadata.tunnelURL, env),
                processId: metadata.processId,
                tunnelURL: metadata.tunnelURL,
            };

            return {
                success: true,
                instance: instanceDetails
            };
        } catch (error) {
            this.logger.error('getInstanceDetails', error, { instanceId });
            return { 
                success: false,
                error: `Failed to get instance details: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    async getInstanceStatus(instanceId: string): Promise<BootstrapStatusResponse> {
        try {
            // Check if instance exists by checking metadata
            const metadata = await this.getInstanceMetadata(instanceId);
            if (!metadata) {
                return {
                    success: false,
                    pending: false,
                    isHealthy: false,
                    error: `Instance ${instanceId} not found`
                };
            }
            
            let isHealthy = true;
            try {
                // Optionally check if process is still running
                if (metadata.processId) {
                    for (let i = 0; i < 3; i++) {
                        try {
                            const processes = await this.getSandbox().listProcesses();
                            const process = processes.find((p: {id: string; status: string}) => p.id === metadata.processId);
                            isHealthy = !!(process && process.status === 'running');
                            break;
                        } catch (error) {
                            this.logger.error(`Failed to check process ${metadata.processId}, retrying...${i + 1}/3`, {error});
                            isHealthy = false; // Process not found or not running
                        }
                    }
                }
            } catch {
                // No preview available
                isHealthy = false;
            }

            return {
                success: true,
                pending: false,
                isHealthy,
                message: isHealthy ? 'Instance is running normally' : 'Instance may have issues',
                previewURL: resolvePreviewUrl(metadata.previewURL, metadata.tunnelURL, env),
                tunnelURL: metadata.tunnelURL,
                processId: metadata.processId
            };
        } catch (error) {
            this.logger.error('getInstanceStatus', error, { instanceId });
            return {
                success: false,
                pending: false,
                isHealthy: false,
                error: `Failed to get instance status: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    async shutdownInstance(instanceId: string): Promise<ShutdownResponse> {
        try {
            // Check if instance exists 
            const metadata = await this.getInstanceMetadata(instanceId);
            if (!metadata) {
                return {
                    success: false,
                    error: `Instance ${instanceId} not found`
                };
            }

            this.logger.info(`Shutting down instance: ${instanceId}`);

            const sandbox = this.getSandbox();
            
            if (metadata.processId) {
                try {
                    await sandbox.killProcess(metadata.processId);
                } catch (error) {
                    this.logger.warn(`Failed to kill process ${metadata.processId}`, error);
                }
            }
            
            // Unexpose the allocated port if we know what it was
            if (metadata.allocatedPort) {
                try {
                    await sandbox.unexposePort(metadata.allocatedPort);
                    this.logger.info(`Unexposed port ${metadata.allocatedPort} for instance ${instanceId}`);
                } catch (error) {
                    this.logger.warn(`Failed to unexpose port ${metadata.allocatedPort}`, error);
                }
            }
            
            // Clean up files
            await this.safeSandboxExec(`rm -rf ${instanceId}`);

            // Invalidate session cache
            this.invalidateSessionCache(instanceId);
            
            // Invalidate metadata cache since instance is being shutdown
            this.invalidateMetadataCache(instanceId);

            return {
                success: true,
                message: `Successfully shutdown instance ${instanceId}`
            };
        } catch (error) {
            this.logger.error('shutdownInstance', error, { instanceId });
            return {
                success: false,
                error: `Failed to shutdown instance: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    // ==========================================
    // FILE OPERATIONS
    // ==========================================

    async writeFiles(instanceId: string, files: WriteFilesRequest['files']): Promise<WriteFilesResponse> {
        try {
            const session = await this.getInstanceSession(instanceId);
            // Filter out donttouch files
            const metadata = await this.getInstanceMetadata(instanceId);
            const donttouchFiles = new Set(metadata.donttouch_files);
            
            const filteredFiles = files.filter(file => !donttouchFiles.has(file.filePath));
            const rawResults = await this.writeFilesBulk(instanceId, filteredFiles);
            const results = rawResults.results;

            // Add files that were not written to results
            const wereDontTouchFiles = files.filter(file => donttouchFiles.has(file.filePath));
            wereDontTouchFiles.forEach(file => {
                results.push({
                    file: file.filePath,
                    success: false,
                    error: 'File is forbidden to be modified'
                });
            });

            if (wereDontTouchFiles.length > 0) {
                this.logger.warn('Files were not written (protected by donttouch_files)', { files: wereDontTouchFiles.map(f => f.filePath) });
            }

            const successCount = results.filter(r => r.success).length;

            // If code files were modified, touch .reload-trigger to trigger a page reload
            // We use .reload-trigger instead of vite.config.ts because:
            // - vite.config.ts triggers a FULL SERVER RESTART (disposes miniflare, causes race condition errors)
            // - .reload-trigger triggers a PAGE RELOAD via WebSocket (server stays running)
            if (successCount > 0 && filteredFiles.some(file => file.filePath.endsWith('.ts') || file.filePath.endsWith('.tsx'))) {
                this.logger.info('Touching .reload-trigger to trigger page reload');
                await session.exec(`touch .reload-trigger`);
            }

            return {
                success: true,
                results,
                message: `Successfully wrote ${successCount}/${files.length} files`
            };
        } catch (error) {
            this.logger.error('writeFiles', error, { instanceId });
            return {
                success: false,
                results: files.map(f => ({ file: f.filePath, success: false, error: 'Instance error' })),
                error: `Failed to write files: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    async getFiles(templateOrInstanceId: string, filePaths?: string[], applyFilter: boolean = true, redactedFiles?: string[]): Promise<GetFilesResponse> {
        try {
            const session = await this.getInstanceSession(templateOrInstanceId);

            if (!filePaths) {
                // Read '.important_files.json' in instance directory
                const importantFiles = await session.exec(`jq -r '.[]' .important_files.json | while read -r path; do if [ -d "$path" ]; then find "$path" -type f; elif [ -f "$path" ]; then echo "$path"; fi; done`);
                this.logger.info(`Read important files: stdout: ${importantFiles.stdout}, stderr: ${importantFiles.stderr}`);
                filePaths = importantFiles.stdout.split('\n').filter((path: string) => path);
                if (!filePaths) {
                    return {
                        success: false,
                        files: [],
                        error: 'Failed to read important files'
                    };
                }
                this.logger.info(`Successfully read important files: ${filePaths}`);
                applyFilter = true;
            }

            let redactedPaths: Set<string> = new Set();

            if (applyFilter) {
                if (redactedFiles) {
                    redactedPaths = new Set(redactedFiles);
                } else {
                    try {
                        const metadata = await this.getInstanceMetadata(templateOrInstanceId);
                        redactedPaths = new Set(metadata.redacted_files);
                    } catch (error) {
                        this.logger.warn('Failed to get redacted files', { templateOrInstanceId });
                    }
                }
            }

            const files = [];
            const errors = [];

            const readPromises = filePaths.map(async (filePath: string) => {
                try {
                    const result = await session.readFile(`/workspace/${templateOrInstanceId}/${filePath}`);
                    return {
                        result,
                        filePath
                    };
                } catch (error) {
                    this.logger.error(`Failed to read file ${filePath}`, { error });
                    return {
                        result: null,
                        filePath,
                        error
                    };
                }
            });
        
            const readResults = await Promise.allSettled(readPromises);
        
            for (const readResult of readResults) {
                if (readResult.status === 'fulfilled') {
                    const { result, filePath } = readResult.value;
                    if (result && result.success) {
                        files.push({
                            filePath: filePath,
                            fileContents: (applyFilter && redactedPaths.has(filePath)) ? '[REDACTED]' : result.content
                        });
                        
                        this.logger.info('File read successfully', { filePath });
                    } else {
                        this.logger.error('File read failed', { filePath });
                        errors.push({
                            file: filePath,
                            error: 'Failed to read file'
                        });
                    }
                } else {
                    this.logger.error(`Promise rejected for file read`);
                    errors.push({
                        file: 'unknown',
                        error: 'Promise rejected'
                    });
                }
            }

            return {
                success: true,
                files,
                errors: errors.length > 0 ? errors : undefined
            };
        } catch (error) {
            this.logger.error('getFiles', error, { templateOrInstanceId });
            return {
                success: false,
                files: [],
                error: `Failed to get files: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }
    // ==========================================
    // LOG RETRIEVAL
    // ==========================================
    async getLogs(instanceId: string, onlyRecent?: boolean, durationSeconds?: number): Promise<GetLogsResponse> {
        try {
            this.logger.info('Retrieving instance logs', { instanceId, durationSeconds });
            // Use CLI to get all logs and reset the file
            const durationArg = durationSeconds ? `--duration ${durationSeconds}` : '';
            const cmd = `timeout 10s monitor-cli logs get -i ${instanceId} --format raw ${onlyRecent ? '--reset' : ''} ${durationArg}`;
            const result = await this.executeCommand(instanceId, cmd, { timeout: 15000 });
            return {
                success: true,
                logs: {
                    stdout: result.stdout,
                    stderr: result.stderr,
                },
                error: undefined
            };
        } catch (error) {
            this.logger.error('getLogs', error, { instanceId });
            return {
                success: false,
                logs: {
                    stdout: '',
                    stderr: '',
                },
                error: `Failed to get logs: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    // ==========================================
    // COMMAND EXECUTION
    // ==========================================

    async executeCommands(instanceId: string, commands: string[], timeout?: number): Promise<ExecuteCommandsResponse> {
        try {
            const results: CommandExecutionResult[] = [];
            
            for (const command of commands) {
                try {
                    const result = await this.executeCommand(instanceId, command, { timeout });
                    
                    results.push({
                        command,
                        success: result.exitCode === 0,
                        output: result.stdout,
                        error: result.stderr || undefined,
                        exitCode: result.exitCode
                    });
                    
                    if (result.exitCode !== 0) {
                        this.logger.error('Command execution failed', { command, error: result.stderr });
                    }
                    
                    this.logger.info('Command executed', { command, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr });
                } catch (error) {
                    this.logger.error('Command execution failed with error', { command, error });
                    results.push({
                        command,
                        success: false,
                        output: '',
                        error: error instanceof Error ? error.message : 'Execution error'
                    });
                }
            }

            const successCount = results.filter(r => r.success).length;
            return {
                success: true,
                results,
                message: `Executed ${successCount}/${commands.length} commands successfully`
            };
        } catch (error) {
            this.logger.error('executeCommands', error, { instanceId });
            return {
                success: false,
                results: commands.map(cmd => ({
                    command: cmd,
                    success: false,
                    output: '',
                    error: 'Instance error' 
                })),
                error: `Failed to execute commands: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    // ==========================================
    // ERROR MANAGEMENT
    // ==========================================

    async getInstanceErrors(instanceId: string, clear?: boolean): Promise<RuntimeErrorResponse> {
        try {
            let errors: RuntimeError[] = [];
            const cmd = `timeout 3s monitor-cli errors list -i ${instanceId} --format json ${clear ? '--reset' : ''}`;
            const result = await this.executeCommand(instanceId, cmd, { timeout: 15000 });
            
            if (result.exitCode === 0) {
                let response: {success: boolean, errors: StoredError[]};
                try {
                    response = JSON.parse(result.stdout);
                    this.logger.info(`getInstanceErrors - ${response.errors.length ? 'errors found' : ''}: ${result.stdout}`);
                } catch (parseError) {
                    this.logger.warn('Failed to parse CLI output as JSON', { stdout: result.stdout });
                    throw new Error('Invalid JSON response from CLI tools');
                }
                if (response.success && response.errors) {
                    // Convert StoredError objects to RuntimeError format
                    errors = response.errors;

                    return {
                        success: true,
                        errors,
                        hasErrors: errors.length > 0
                    };
                }
            } 
            this.logger.error(`Failed to get errors for instance ${instanceId}: STDERR: ${result.stderr}, STDOUT: ${result.stdout}`);

            return {
                success: false,
                errors: [],
                hasErrors: false,
                error: `Failed to get errors for instance ${instanceId}: STDERR: ${result.stderr}, STDOUT: ${result.stdout}`
            };
        } catch (error) {
            this.logger.error('getInstanceErrors', error, { instanceId });
            return {
                success: false,
                errors: [],
                hasErrors: false,
                error: `Failed to get errors: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    async clearInstanceErrors(instanceId: string): Promise<ClearErrorsResponse> {
        try {
            // Try enhanced error system first - clear ALL errors
            try {
                const cmd = `timeout 10s monitor-cli errors clear -i ${instanceId} --confirm`;
                const result = await this.executeCommand(instanceId, cmd, { timeout: 15000 }); // 15 second timeout
                
                if (result.exitCode === 0) {
                    let response: any;
                    try {
                        response = JSON.parse(result.stdout);
                    } catch (parseError) {
                        this.logger.warn('Failed to parse CLI output as JSON', { stdout: result.stdout });
                        throw new Error('Invalid JSON response from CLI tools');
                    }
                    if (response.success) {
                        return {
                            success: true,
                            message: response.message || `Cleared ${response.clearedCount || 0} errors`
                        };
                    }
                }
            } catch (enhancedError) {
                this.logger.warn('Error clearing unavailable, falling back to legacy', enhancedError);
            }

            this.logger.info(`Cleared errors for instance ${instanceId}`);

            return {
                success: true,
                message: `Cleared errors`
            };
        } catch (error) {
            this.logger.error('clearInstanceErrors', error, { instanceId });
            return {
                success: false,
                error: `Failed to clear errors: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    // ==========================================
    // CODE ANALYSIS & FIXING
    // ==========================================

    async runStaticAnalysisCode(instanceId: string): Promise<StaticAnalysisResponse> {
        try {
            const lintIssues: CodeIssue[] = [];
            const typecheckIssues: CodeIssue[] = [];
            
            // Run ESLint and TypeScript check in parallel
            const [lintResult, tscResult] = await Promise.allSettled([
                this.executeCommand(instanceId, 'bun run lint'),
                this.executeCommand(instanceId, 'bunx tsc -b --incremental --noEmit --pretty false')
            ]);

            const results: StaticAnalysisResponse = {
                success: true,
                lint: {
                    issues: [],
                    summary: {
                        errorCount: 0,
                        warningCount: 0,
                        infoCount: 0
                    },
                    rawOutput: ''
                },
                typecheck: {
                    issues: [],
                    summary: {
                        errorCount: 0,
                        warningCount: 0,
                        infoCount: 0
                    },
                    rawOutput: ''
                }
            };
            
            // Process ESLint results
            if (lintResult.status === 'fulfilled') {
                try {
                    const lintData = JSON.parse(lintResult.value.stdout) as Array<{
                        filePath: string;
                        messages: Array<{
                            message: string;
                            line?: number;
                            column?: number;
                            severity: number;
                            ruleId?: string;
                        }>;
                    }>;
                    
                    for (const fileResult of lintData) {
                        for (const message of fileResult.messages || []) {
                            lintIssues.push({
                                message: message.message,
                                filePath: fileResult.filePath,
                                line: message.line || 0,
                                column: message.column,
                                severity: this.mapESLintSeverity(message.severity),
                                ruleId: message.ruleId || '',
                                source: 'eslint'
                            });
                        }
                    }
                } catch (error) {
                    this.logger.warn('Failed to parse ESLint output', error);
                }

                results.lint.issues = lintIssues;
                results.lint.summary = {
                    errorCount: lintIssues.filter(issue => issue.severity === 'error').length,
                    warningCount: lintIssues.filter(issue => issue.severity === 'warning').length,
                    infoCount: lintIssues.filter(issue => issue.severity === 'info').length
                };
                results.lint.rawOutput = `STDOUT: ${lintResult.value.stdout}\nSTDERR: ${lintResult.value.stderr}`;
            } else if (lintResult.status === 'rejected') {
                this.logger.warn('ESLint analysis failed', lintResult.reason);
            }
            
            // Process TypeScript check results
            if (tscResult.status === 'fulfilled') {
                try {
                    // TypeScript errors can come from either stdout or stderr
                    const output = tscResult.value.stderr || tscResult.value.stdout;
                    
                    if (!output || output.trim() === '') {
                        this.logger.info('No TypeScript output to parse');
                    } else {
                        this.logger.info(`Parsing TypeScript output: ${output.substring(0, 200)}...`);
                        
                        // Split by lines and parse each error
                        const lines = output.split('\n');
                        let currentError: any = null;
                        
                        for (const line of lines) {
                            // Match TypeScript error format: path(line,col): error TSxxxx: message
                            const match = line.match(/^(.+?)\((\d+),(\d+)\): error TS(\d+): (.*)$/);
                            if (match) {
                                // If we have a previous error being built, add it
                                if (currentError) {
                                    typecheckIssues.push(currentError);
                                }
                                
                                // Start building new error
                                currentError = {
                                    message: match[5].trim(),
                                    filePath: match[1].trim(),
                                    line: parseInt(match[2]),
                                    column: parseInt(match[3]),
                                    severity: 'error' as const,
                                    source: 'typescript',
                                    ruleId: `TS${match[4]}`
                                };
                                
                                this.logger.info(`Found TypeScript error: ${currentError.filePath}:${currentError.line} - ${currentError.ruleId}`);
                            } else if (currentError && line.trim() && !line.startsWith('src/') && !line.includes(': error TS')) {
                                // This might be a continuation of the error message
                                currentError.message += ' ' + line.trim();
                            }
                        }
                        
                        // Add the last error if it exists
                        if (currentError) {
                            typecheckIssues.push(currentError);
                        }
                        
                        this.logger.info(`Parsed ${typecheckIssues.length} TypeScript errors`);
                    }
                } catch (error) {
                    this.logger.warn('Failed to parse TypeScript output', error);
                }
                
                results.typecheck.issues = typecheckIssues;
                results.typecheck.summary = {
                    errorCount: typecheckIssues.filter(issue => issue.severity === 'error').length,
                    warningCount: typecheckIssues.filter(issue => issue.severity === 'warning').length,
                    infoCount: typecheckIssues.filter(issue => issue.severity === 'info').length
                };
                results.typecheck.rawOutput = `STDOUT: ${tscResult.value.stdout}\nSTDERR: ${tscResult.value.stderr}`;
            } else if (tscResult.status === 'rejected') {
                this.logger.warn('TypeScript analysis failed', tscResult.reason);
            }

            this.logger.info(`Analysis completed: ${lintIssues.length} lint issues, ${typecheckIssues.length} typecheck issues`);

            return {
                ...results
            };
        } catch (error) {
            this.logger.error('runStaticAnalysisCode', error, { instanceId });
            return {
                success: false,
                lint: { issues: [] },
                typecheck: { issues: [] },
                error: `Failed to run analysis: ${error instanceof Error ? error.message : 'Unknown error'}`
            };
        }
    }

    private mapESLintSeverity(severity: number): LintSeverity {
        switch (severity) {
            case 1: return 'warning';
            case 2: return 'error';
            default: return 'info';
        }
    }

    // ==========================================
    // DEPLOYMENT
    // ==========================================
    async deployToCloudflareWorkers(instanceId: string, target: DeploymentTarget = 'platform'): Promise<DeploymentResult> {
        try {
            this.logger.info('Starting deployment', { instanceId });
            
            // Get project metadata
            const metadata = await this.getInstanceMetadata(instanceId);
            const projectName = metadata?.projectName || instanceId;
            
            // Get credentials from environment (secure - no exposure to external processes)
            const accountId = env.CLOUDFLARE_ACCOUNT_ID;
            const apiToken = env.CLOUDFLARE_API_TOKEN;
            
            if (!accountId || !apiToken) {
                throw new Error('CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN must be set in environment');
            }
            
            this.logger.info('Processing deployment', { instanceId });
            
            // Step 1: Run build commands (bun run build && bunx wrangler build)
            this.logger.info('Building project');
            const buildResult = await this.executeCommand(instanceId, 'bun run build');
            if (buildResult.exitCode !== 0) {
                this.logger.warn('Build step failed or not available', buildResult.stdout, buildResult.stderr);
                throw new Error(`Build failed: ${buildResult.stderr}`);
            }
            
            const wranglerBuildResult = await this.executeCommand(instanceId, 'bunx wrangler build');
            if (wranglerBuildResult.exitCode !== 0) {
                this.logger.warn('Wrangler build failed', wranglerBuildResult.stdout, wranglerBuildResult.stderr);
                // Continue anyway - some projects might not need wrangler build
            }
            
            // Step 2: Parse wrangler config from KV
            this.logger.info('Reading wrangler configuration from KV');
            const wranglerConfigContent = await env.VibecoderStore.get(this.getWranglerKVKey(instanceId));
            
            if (!wranglerConfigContent) {
                // This should never happen unless KV itself has some issues
                throw new Error(`Wrangler config not found in KV for ${instanceId}`);
            } else {
                this.logger.info('Using wrangler configuration from KV');
            }
            
            const config = parseWranglerConfig(wranglerConfigContent);
            
            this.logger.info('Worker configuration', { scriptName: config.name });
            this.logger.info('Worker compatibility', { compatibilityDate: config.compatibility_date });
            
            // Step 3: Read worker script from dist
            this.logger.info('Reading worker script');
            const session = await this.getInstanceSession(instanceId);
            const workerFile = await session.readFile(`/workspace/${instanceId}/dist/index.js`);
            if (!workerFile.success) {
                throw new Error(`Worker script not found at /${instanceId}/dist/index.js. Please build the project first.`);
            }
            
            const workerContent = workerFile.content;
            this.logger.info('Worker script loaded', { sizeKB: (workerContent.length / 1024).toFixed(2) });
            
            // Step 3a: Check for additional worker modules (ESM imports)
            // Process them the same way as assets but as strings for the Map
            let additionalModules: Map<string, string> | undefined;
            try {
                const workerAssetsPath = `${instanceId}/dist/assets`;
                const workerAssetsResult = await this.safeSandboxExec(`test -d ${workerAssetsPath} && echo "exists" || echo "missing"`);
                const hasWorkerAssets = workerAssetsResult.exitCode === 0 && workerAssetsResult.stdout.trim() === "exists";
                
                if (hasWorkerAssets) {
                    this.logger.info('Processing additional worker modules', { workerAssetsPath });
                    
                    // Find all JS files in the worker assets directory
                    const findResult = await this.safeSandboxExec(`find ${workerAssetsPath} -type f -name "*.js"`);
                    if (findResult.exitCode === 0) {
                        const modulePaths = findResult.stdout.trim().split('\n').filter((path: string) => path.trim());
                        
                        if (modulePaths.length > 0) {
                            additionalModules = new Map<string, string>();
                            
                            for (const fullPath of modulePaths) {
                                const relativePath = fullPath.replace(`${instanceId}/dist/`, '');
                                
                                try {
                                    const buffer = await this.readFileAsBase64Buffer(fullPath);
                                    const moduleContent = buffer.toString('utf8');
                                    additionalModules.set(relativePath, moduleContent);
                                    
                                    this.logger.info('Worker module loaded', { 
                                        path: relativePath, 
                                        sizeKB: (moduleContent.length / 1024).toFixed(2) 
                                    });
                                } catch (error) {
                                    this.logger.warn(`Failed to read worker module ${fullPath}:`, error);
                                }
                            }
                            
                            if (additionalModules.size > 0) {
                                this.logger.info('Found additional worker modules', { count: additionalModules.size });
                            }
                        }
                    }
                }
            } catch (error) {
                this.logger.error('Failed to process additional worker modules:', error);
            }
            
            // Step 4: Check for static assets and process them
            const assetsPath = `${instanceId}/dist/client`;
            let assetsManifest: Record<string, { hash: string; size: number }> | undefined;
            let fileContents: Map<string, Buffer> | undefined;
            
            const assetDirResult = await this.safeSandboxExec(`test -d ${assetsPath} && echo "exists" || echo "missing"`);
            const hasAssets = assetDirResult.exitCode === 0 && assetDirResult.stdout.trim() === "exists";
            
            if (hasAssets) {
                this.logger.info('Processing static assets', { assetsPath });
                const assetProcessResult = await this.processAssetsInSandbox(instanceId, assetsPath);
                assetsManifest = assetProcessResult.assetsManifest;
                fileContents = assetProcessResult.fileContents;
            } else {
                this.logger.info('No static assets found, deploying worker only');
            }
            
            // Step 5: Override config for dispatch deployment
            const dispatchConfig = {
                ...config,
                name: config.name
            };
        
            
            // Step 6: Build deployment config using pure function
            const deployConfig = buildDeploymentConfig(
                dispatchConfig,
                workerContent,
                accountId,
                apiToken,
                assetsManifest,
                config.compatibility_flags
            );
            
            // Step 7: Deploy using pure function
            const useDispatch = target === 'platform';
            this.logger.info('Deploying to Cloudflare', { target });
            
            if (useDispatch) {
                if (!('DISPATCH_NAMESPACE' in env)) {
                    throw new Error('DISPATCH_NAMESPACE not found in environment variables, cannot deploy without dispatch namespace');
                }
                
                this.logger.info('Using dispatch namespace', { dispatchNamespace: env.DISPATCH_NAMESPACE });
                await deployToDispatch(
                    {
                        ...deployConfig,
                        dispatchNamespace: env.DISPATCH_NAMESPACE as string
                    },
                    fileContents,
                    additionalModules,
                    config.migrations,
                    config.assets
                );
            } else {
                await deployWorker(
                    deployConfig,
                    fileContents,
                    additionalModules,
                    config.migrations,
                    config.assets
                );
            }
            
            // Step 8: Determine deployment URL
            const deployedUrl = `${this.getProtocolForHost()}://${projectName}.${getPreviewDomain(env)}`;
            const deploymentId = projectName;
            
            this.logger.info('Deployment successful', { 
                instanceId,
                deployedUrl, 
                deploymentId,
                mode: useDispatch ? 'dispatch-namespace' : 'user-worker'
            });
            
            return {
                success: true,
                message: `Successfully deployed ${instanceId} using secure API deployment`,
                deployedUrl,
                deploymentId,
                output: `Deployed`
            };
            
        } catch (error) {
            this.logger.error('deployToCloudflareWorkers', error, { instanceId });
            return {
                success: false,
                message: `Deployment failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
                error: error instanceof Error ? error.message : 'Unknown error'
            };
        }
    }
    
    /**
     * Process static assets in sandbox and create manifest for deployment
     */
    private async processAssetsInSandbox(_instanceId: string, assetsPath: string): Promise<{
        assetsManifest: Record<string, { hash: string; size: number }>;
        fileContents: Map<string, Buffer>;
    }> {
        // Get list of all files in assets directory
        const findResult = await this.safeSandboxExec(`find ${assetsPath} -type f`);
        if (findResult.exitCode !== 0) {
            throw new Error(`Failed to list assets: ${findResult.stderr}`);
        }
        
        const filePaths = findResult.stdout.trim().split('\n').filter((path: string) => path);
        this.logger.info('Asset files found', { count: filePaths.length });
        
        const fileContents = new Map<string, Buffer>();
        const filesAsArrayBuffer = new Map<string, ArrayBuffer>();
        
        // Read each file and calculate hashes
        for (const fullPath of filePaths) {
            const relativePath = fullPath.replace(`${assetsPath}/`, '/');
            
            try {
                // Use base64 encoding to preserve binary files and Unicode
                const buffer = await this.readFileAsBase64Buffer(fullPath);
                fileContents.set(relativePath, buffer);
                
                const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
                filesAsArrayBuffer.set(relativePath, arrayBuffer);
                
                this.logger.info('Asset file processed', { path: relativePath, sizeBytes: buffer.length });
            } catch (error) {
                this.logger.warn(`Failed to read asset file ${fullPath}:`, error);
            }
        }
        
        // Create asset manifest using pure function
        const assetsManifest = await createAssetManifest(filesAsArrayBuffer);
        const assetCount = Object.keys(assetsManifest).length;
        this.logger.info('Asset manifest created', { assetCount });
        
        return { assetsManifest, fileContents };
    }
    
    /**
     * Read file from sandbox as base64 and convert to Buffer
     * Uses default session for deployment file operations with absolute paths
     */
    private async readFileAsBase64Buffer(filePath: string): Promise<Buffer> {
        // Use base64 with no line wrapping (-w 0) to preserve binary data
        const base64Result = await this.safeSandboxExec(`base64 -w 0 "${filePath}"`);
        if (base64Result.exitCode !== 0) {
            throw new Error(`Failed to encode file: ${base64Result.stderr}`);
        }
        
        return Buffer.from(base64Result.stdout, 'base64');
    }

    /**
     * Get protocol for host (utility method)
     */
    private getProtocolForHost(): string {
        // Simple heuristic - use https for production-like domains
        const previewDomain = getPreviewDomain(env);
        if (previewDomain.includes('localhost') || previewDomain.includes('127.0.0.1')) {
            return 'http';
        }
        return 'https';
    }
}