import fs from 'fs'; import { spawn, ChildProcess, execFileSync, execSync } from 'child_process'; import path from 'path'; import Docker from 'dockerode'; import { EventEmitter } from 'events'; import { IdxEngine } from '../idx/idx-engine'; import { buildWorkspaceImage, loadWorkspaceConfig, type CodeverseConfig } from './builder'; import { createDockerClient, probeDockerAvailability, resolveDockerClient } from './client'; import { HFStorage } from '../hf/storage'; import { resolveSafeProjectPath } from '../fs/isolation'; import { ENV_CONFIG } from '../env-config'; /** * Lighter process interface for reconnected sessions that don't have a full Node.js ChildProcess object. * We only require 'kill' and 'pid' for the watchdog and management tasks. */ type WorkspaceProcess = Pick & { on?: ChildProcess['on']; stdout?: ChildProcess['stdout']; stderr?: ChildProcess['stderr']; }; type WorkspaceRuntimeKind = 'native' | 'docker'; interface NativeWorkspaceRuntimeEntry { kind: 'native'; pid: number; port: number; process: WorkspaceProcess; } interface DockerWorkspaceRuntimeEntry { kind: 'docker'; containerId: string; containerName: string; imageName: string; port: number; socketPath: string; } type WorkspaceRuntimeEntry = NativeWorkspaceRuntimeEntry | DockerWorkspaceRuntimeEntry; /** * Registry for native workspace processes (IDE instances running outside Docker) * Map */ export const nativeProcesses = new Map(); export const dockerWorkspaces = new Map(); /** * Internal Provisioning Bus to multicast logs to concurrent clients. */ class ProvisioningBus extends EventEmitter {} export const provisioningBus = new ProvisioningBus(); /** * Map to track active provisioning promises to prevent redundant creation loops. */ export const pendingProvisioning = new Map>(); interface WorkspaceRuntimePaths { fullWorkspaceId: string; shortWorkspaceId: string; projectPath: string; runtimeRootPath: string; runtimeWorkspacePath: string; userDataPath: string; metadataPath: string; npmCachePath: string; } interface CodeServerLaunch { command: string; args: string[]; label: string; usesNpx: boolean; useShell: boolean; } const SHORT_WORKSPACE_ID_LENGTH = 8; const RUNTIME_ROOT_DIR_NAME = '.codeverse-runtime'; const DOCKER_CODE_SERVER_PORT = 8080; const WORKSPACE_CONTAINER_NAME_PREFIX = 'codeverse-workspace-'; const WORKSPACE_RUNTIME_LABEL = 'codeverse.runtime'; const WORKSPACE_RUNTIME_LABEL_VALUE = 'workspace'; const WORKSPACE_ID_LABEL = 'codeverse.workspace.id'; function getWorkspaceRootPath(): string { return ENV_CONFIG.WORKSPACE_ROOT; } function getRuntimeRootPath(): string { return path.join(/*turbopackIgnore: true*/ getWorkspaceRootPath(), RUNTIME_ROOT_DIR_NAME); } function getShortWorkspaceId(id: string): string { return id.slice(0, SHORT_WORKSPACE_ID_LENGTH); } function isPathWithinParent(parentPath: string, targetPath: string): boolean { const normalizedParent = path.resolve(parentPath); const normalizedTarget = path.resolve(targetPath); return normalizedTarget === normalizedParent || normalizedTarget.startsWith(`${normalizedParent}${path.sep}`); } function getNativeWorkspaceEntry(id: string): NativeWorkspaceRuntimeEntry | undefined { const directEntry = nativeProcesses.get(id); if (directEntry) { return directEntry; } const prefixedKey = Array.from(nativeProcesses.keys()).find((key) => id.startsWith(key)); return prefixedKey ? nativeProcesses.get(prefixedKey) : undefined; } function getDockerWorkspaceEntry(id: string): DockerWorkspaceRuntimeEntry | undefined { return dockerWorkspaces.get(id); } function getWorkspaceRuntimeEntry(id: string): WorkspaceRuntimeEntry | undefined { return getNativeWorkspaceEntry(id) ?? getDockerWorkspaceEntry(id); } function getWorkspaceContainerName(id: string): string { return `${WORKSPACE_CONTAINER_NAME_PREFIX}${id}`; } function getWorkspaceContainerLabels(config: Pick): Record { return { [WORKSPACE_RUNTIME_LABEL]: WORKSPACE_RUNTIME_LABEL_VALUE, [WORKSPACE_ID_LABEL]: config.id, 'codeverse.workspace.project': config.projectName, 'codeverse.workspace.user': config.userId, }; } function getDockerBindSourcePath(hostPath: string): string { return path.resolve(hostPath); } function hasCustomDockerPackages(config: CodeverseConfig): boolean { return Boolean( config.packages?.apt?.length || config.packages?.npm?.length ); } function getWorkspaceEnvironment(config: CodeverseConfig): string[] { return Object.entries(config.env ?? {}).map(([key, value]) => `${key}=${value}`); } async function ensureDockerImageAvailable(docker: Docker, imageName: string, log: (msg: string) => void): Promise { try { await docker.getImage(imageName).inspect(); return; } catch { log(`Pulling Docker image '${imageName}'...`); } await new Promise((resolve, reject) => { docker.pull(imageName, (error: Error | null, stream?: NodeJS.ReadableStream) => { if (error || !stream) { reject(error ?? new Error(`Failed to pull Docker image '${imageName}'`)); return; } docker.modem.followProgress( stream, (progressError: Error | null) => { if (progressError) { reject(progressError); return; } resolve(); }, (event: { status?: string; id?: string }) => { if (event.status) { const progressLabel = event.id ? `${event.id}: ${event.status}` : event.status; log(`[DOCKER:PULL] ${progressLabel}`); } } ); }); }); } function getPublishedPortForContainerInfo(info: Docker.ContainerInspectInfo): number | undefined { const bindings = info.NetworkSettings?.Ports?.[`${DOCKER_CODE_SERVER_PORT}/tcp`]; const hostPort = bindings?.[0]?.HostPort; if (!hostPort) { return undefined; } const parsedPort = Number.parseInt(hostPort, 10); return Number.isFinite(parsedPort) ? parsedPort : undefined; } function getPublishedPortForListEntry(containerInfo: Docker.ContainerInfo): number | undefined { const publishedPort = containerInfo.Ports.find((portInfo) => portInfo.PrivatePort === DOCKER_CODE_SERVER_PORT && portInfo.PublicPort)?.PublicPort; return publishedPort && Number.isFinite(publishedPort) ? publishedPort : undefined; } async function resolveWorkspaceRuntimePaths(config: Pick): Promise { const shortWorkspaceId = getShortWorkspaceId(config.id); const runtimeRootPath = getRuntimeRootPath(); const projectPath = await resolveSafeProjectPath(config.userId, config.projectName); return { fullWorkspaceId: config.id, shortWorkspaceId, projectPath, runtimeRootPath, runtimeWorkspacePath: path.join(/*turbopackIgnore: true*/ runtimeRootPath, shortWorkspaceId), userDataPath: path.join(/*turbopackIgnore: true*/ runtimeRootPath, `${shortWorkspaceId}-userdata`), metadataPath: path.join(/*turbopackIgnore: true*/ runtimeRootPath, `${shortWorkspaceId}.id`), npmCachePath: path.join(/*turbopackIgnore: true*/ runtimeRootPath, 'npm-cache'), }; } function ensureRuntimeWorkspacePath(paths: WorkspaceRuntimePaths, log?: (msg: string) => void): string { if (!fs.existsSync(paths.projectPath)) { fs.mkdirSync(paths.projectPath, { recursive: true }); } fs.mkdirSync(paths.runtimeRootPath, { recursive: true }); fs.mkdirSync(paths.userDataPath, { recursive: true }); fs.mkdirSync(paths.npmCachePath, { recursive: true }); if (fs.existsSync(paths.runtimeWorkspacePath)) { try { const existingTargetPath = fs.realpathSync(paths.runtimeWorkspacePath); if (path.resolve(existingTargetPath) === path.resolve(paths.projectPath)) { fs.writeFileSync(paths.metadataPath, paths.fullWorkspaceId); return paths.runtimeWorkspacePath; } } catch { // Fall through and repair the runtime alias. } if (!isPathWithinParent(paths.runtimeRootPath, paths.runtimeWorkspacePath)) { throw new Error(`Unsafe runtime workspace path: ${paths.runtimeWorkspacePath}`); } fs.rmSync(paths.runtimeWorkspacePath, { recursive: true, force: true }); } try { fs.symlinkSync(paths.projectPath, paths.runtimeWorkspacePath, process.platform === 'win32' ? 'junction' : 'dir'); fs.writeFileSync(paths.metadataPath, paths.fullWorkspaceId); log?.(`Bound runtime alias ${paths.shortWorkspaceId} -> ${paths.projectPath}`); return paths.runtimeWorkspacePath; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); fs.writeFileSync(paths.metadataPath, paths.fullWorkspaceId); log?.(`[WARN] Runtime alias creation failed. Falling back to direct project path: ${errorMessage}`); return paths.projectPath; } } /** * Checks if a native workspace is currently running. * Supports fuzzy matching for reconnected sessions that might use a prefix key. */ export function isNativeWorkspaceRunning(id: string): boolean { if (pendingProvisioning.has(id)) return false; return getNativeWorkspaceEntry(id) !== undefined; } export function isWorkspaceRunning(id: string): boolean { if (pendingProvisioning.has(id)) return false; return getWorkspaceRuntimeEntry(id) !== undefined; } /** * Returns the current runtime status of a workspace. */ export function getWorkspaceStatus(id: string): "ready" | "provisioning" | "offline" { if (pendingProvisioning.has(id)) return "provisioning"; if (getWorkspaceRuntimeEntry(id)) return "ready"; return "offline"; } /** * Helper for async delays. */ const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); /** * Finds an available port in the 8100-9000 range. */ function findAvailablePort(): number { const occupiedPorts = [ ...Array.from(nativeProcesses.values()).map((entry) => entry.port), ...Array.from(dockerWorkspaces.values()).map((entry) => entry.port), ]; let port = Math.floor(Math.random() * (9000 - 8100) + 8100); while (occupiedPorts.includes(port)) { port = Math.floor(Math.random() * (9000 - 8100) + 8100); } return port; } function resolveExecutableOnPath(candidates: string[]): string | null { const locatorCommand = process.platform === 'win32' ? 'where' : 'which'; for (const candidate of candidates) { try { const output = execFileSync(locatorCommand, [candidate], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], }); const resolvedPath = output .split(/\r?\n/) .map((line) => line.trim()) .find(Boolean); if (resolvedPath) { return resolvedPath; } } catch { // Fall through to the next candidate. } } return null; } function resolveLocalToolNodeBinary(): string | null { if (ENV_CONFIG.CODE_SERVER_NODE_BIN && fs.existsSync(ENV_CONFIG.CODE_SERVER_NODE_BIN)) { return ENV_CONFIG.CODE_SERVER_NODE_BIN; } const localToolsRoot = path.join(process.cwd(), '.codeverse-tools'); if (!fs.existsSync(localToolsRoot)) { return null; } const directNodePath = path.join(localToolsRoot, 'node', 'node.exe'); if (fs.existsSync(directNodePath)) { return directNodePath; } const nodeDirs = fs.readdirSync(localToolsRoot, { withFileTypes: true }) .filter((entry) => entry.isDirectory() && /^node-v22\..*-win-x64$/i.test(entry.name)) .map((entry) => path.join(localToolsRoot, entry.name, 'node.exe')); return nodeDirs.find((candidate) => fs.existsSync(candidate)) ?? null; } function resolveLocalCodeServerEntry(): string | null { if (ENV_CONFIG.CODE_SERVER_ENTRY && fs.existsSync(ENV_CONFIG.CODE_SERVER_ENTRY)) { return ENV_CONFIG.CODE_SERVER_ENTRY; } const localEntryPath = path.join(process.cwd(), '.codeverse-tools', 'code-server', 'node_modules', 'code-server', 'out', 'node', 'entry.js'); return fs.existsSync(localEntryPath) ? localEntryPath : null; } function getCurrentNodeMajorVersion(): number { const [majorVersion] = process.versions.node.split('.'); return Number.parseInt(majorVersion ?? '0', 10); } function resolveCodeServerLaunch(): CodeServerLaunch { const overrideBinary = process.env.CODE_SERVER_BIN; if (overrideBinary) { const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(overrideBinary); return { command: overrideBinary, args: [], label: overrideBinary, usesNpx: false, useShell }; } if (process.platform === 'win32') { const localToolNodeBinary = resolveLocalToolNodeBinary(); const localCodeServerEntry = resolveLocalCodeServerEntry(); if (localToolNodeBinary && localCodeServerEntry) { return { command: localToolNodeBinary, args: [localCodeServerEntry], label: 'local code-server toolchain', usesNpx: false, useShell: false }; } const codeServerBinary = resolveExecutableOnPath(['code-server.exe', 'code-server']); if (codeServerBinary) { return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false }; } if (getCurrentNodeMajorVersion() !== 22) { throw new Error('CODE_SERVER_WINDOWS_REQUIRES_NODE_22_OR_LOCAL_TOOLCHAIN'); } const npxCliPath = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js'); if (fs.existsSync(npxCliPath)) { return { command: process.execPath, args: [npxCliPath, '--yes', 'code-server'], label: 'node npx-cli.js code-server', usesNpx: true, useShell: false }; } throw new Error('CODE_SERVER_BIN_NOT_FOUND'); } const codeServerBinary = resolveExecutableOnPath(['code-server']); if (codeServerBinary) { return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false }; } const npxBinary = resolveExecutableOnPath(['npx']); if (npxBinary) { return { command: npxBinary, args: ['--yes', 'code-server'], label: 'npx code-server', usesNpx: true, useShell: false }; } throw new Error('CODE_SERVER_BIN_NOT_FOUND'); } /** * Checks if Docker is available in the current environment. */ export async function isDockerAvailable(): Promise<{ available: boolean; reason?: string }> { if (process.env.SIMULATE_HF === 'true') { return { available: false, reason: "Hugging Face Simulation Mode (Artificial Sandbox)" }; } if (process.env.SPACE_ID) { return { available: false, reason: "Hugging Face Space (Native Sandboxed)" }; } const availability = await probeDockerAvailability(ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS); return availability.available ? { available: true } : { available: false, reason: availability.reason ?? "Docker daemon unreachable" }; } /** * Stops a native workspace process. */ export async function stopNativeWorkspace(id: string): Promise { const entry = nativeProcesses.get(id); if (entry) { try { entry.process.kill(); nativeProcesses.delete(id); return true; } catch (e) { console.error(`[MANAGER] Failed to kill code-server ${id}:`, e); nativeProcesses.delete(id); } } return false; } /** * Gets the internal port for a native workspace process. * Supports fuzzy matching for reconnected sessions. */ export function getNativeWorkspacePort(id: string): number | undefined { if (pendingProvisioning.has(id)) { return undefined; } return getNativeWorkspaceEntry(id)?.port; } export function getWorkspacePort(id: string): number | undefined { if (pendingProvisioning.has(id)) { return undefined; } return getWorkspaceRuntimeEntry(id)?.port; } /** * Returns the global unified Android VNC port. */ export function getAndroidPort(): number | undefined { return 6080; } export interface WorkspaceConfig { id: string; userId: string; projectName: string; image?: string; withAndroidEmulator?: boolean; onLog?: (msg: string) => void; } /** * Results for workspace operations. */ export interface WorkspaceOperationResult { success: boolean; containerId?: string; androidContainerId?: string; androidPort?: number; port?: string | number; appetizeUrl?: string; error?: string; runtime?: WorkspaceRuntimeKind; status?: 'hydrating' | 'ready'; } /** * PREDICTIVE HYDRATION: Pre-warms Nix profile and SDKs. */ export async function prewarmWorkspace(config: WorkspaceConfig): Promise { const runtimePaths = await resolveWorkspaceRuntimePaths(config); const workspacePath = ensureRuntimeWorkspacePath(runtimePaths); const idxConfig = IdxEngine.getIdxConfig(workspacePath); if (idxConfig) { // Run Nix sync in background if not already warmed IdxEngine.syncNixEnvironment(workspacePath, idxConfig, (msg) => { provisioningBus.emit(`log:${config.id}`, `[HYDRATE] ${msg}`); }); } } interface WorkspaceHeartbeatOptions { port: number; log: (msg: string) => void; getFailureReason?: () => Promise; } async function waitForWorkspaceHeartbeat({ port, log, getFailureReason }: WorkspaceHeartbeatOptions): Promise { let attempts = 0; while (attempts < 60) { const failureReason = await getFailureReason?.(); if (failureReason) { log(`[FATAL] IDE bootstrap aborted before handshake: ${failureReason}`); return failureReason; } const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); try { const response = await fetch(`http://127.0.0.1:${port}`, { signal: controller.signal }); if (response.ok) { log(`Handshake verified. Studio Engine Online.`); return null; } } catch { if (attempts % 5 === 0) log(`[INFO] Scanning for IDE heartbeat... (Attempt ${attempts}/60)`); if (attempts === 15) { const bootstrapHint = ENV_CONFIG.IDX_NIX_SYNC_ENABLED ? 'Nix evaluation in progress. Cold boot detected.' : 'IDE bootstrap still in progress.'; log(`[INFO] ${bootstrapHint}`); } if (attempts === 45) log(`[WARN] Handshake threshold approaching. IDE core high load.`); } finally { clearTimeout(timeoutId); } await delay(1000); attempts++; } log(`[FATAL] Handshake timeout on 127.0.0.1:${port}.`); return "IDE_HANDSHAKE_TIMEOUT"; } async function resolveExistingWorkspaceContainer(docker: Docker, workspaceId: string): Promise { const containerName = getWorkspaceContainerName(workspaceId); const containers = await docker.listContainers({ all: true, filters: { name: [containerName], }, }); const exactMatch = containers.find((container) => container.Names.some((name) => name === `/${containerName}`)); return exactMatch ?? containers[0] ?? null; } function registerDockerWorkspaceRuntime( workspaceId: string, containerInfo: Docker.ContainerInspectInfo, socketPath: string ): DockerWorkspaceRuntimeEntry | null { const publishedPort = getPublishedPortForContainerInfo(containerInfo); if (!publishedPort) { return null; } const containerEntry: DockerWorkspaceRuntimeEntry = { kind: 'docker', containerId: containerInfo.Id, containerName: containerInfo.Name.replace(/^\//, ''), imageName: containerInfo.Config.Image, port: publishedPort, socketPath, }; dockerWorkspaces.set(workspaceId, containerEntry); return containerEntry; } async function startDockerWorkspace( config: WorkspaceConfig, runtimePaths: WorkspaceRuntimePaths, workspacePath: string, log: (msg: string) => void ): Promise { const dockerResolution = await resolveDockerClient(ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS); if (!dockerResolution) { return null; } const { docker, socketPath } = dockerResolution; const workspaceConfig = await loadWorkspaceConfig(workspacePath); const containerName = getWorkspaceContainerName(config.id); const existingContainerInfo = await resolveExistingWorkspaceContainer(docker, config.id); if (existingContainerInfo) { const existingContainer = docker.getContainer(existingContainerInfo.Id); const existingInspect = await existingContainer.inspect(); if (!existingInspect.State.Running) { log(`Restarting existing Docker workspace container '${containerName}'...`); await existingContainer.start(); } else { log(`Reusing active Docker workspace container '${containerName}'.`); } const runningInspect = await existingContainer.inspect(); const runningEntry = registerDockerWorkspaceRuntime(config.id, runningInspect, socketPath); if (!runningEntry) { return { success: false, error: 'DOCKER_PORT_BINDING_MISSING' }; } const heartbeatError = await waitForWorkspaceHeartbeat({ port: runningEntry.port, log, getFailureReason: async () => { const info = await existingContainer.inspect(); return info.State.Running ? null : `DOCKER_CONTAINER_EXITED_${info.State.ExitCode}`; }, }); if (heartbeatError) { dockerWorkspaces.delete(config.id); return { success: false, error: heartbeatError }; } return { success: true, containerId: runningEntry.containerId, port: runningEntry.port, runtime: 'docker', }; } let imageName = ENV_CONFIG.DOCKER_WORKSPACE_BASE_IMAGE; if (hasCustomDockerPackages(workspaceConfig)) { const buildResult = await buildWorkspaceImage(config.id, workspacePath, log, docker); imageName = buildResult.imageName; } await ensureDockerImageAvailable(docker, imageName, log); const port = findAvailablePort(); const workspaceContainer = await docker.createContainer({ name: containerName, Image: imageName, WorkingDir: '/home/coder/project', Env: [ 'HOME=/home/coder', 'SHELL=/bin/bash', ...getWorkspaceEnvironment(workspaceConfig), ], Labels: getWorkspaceContainerLabels(config), ExposedPorts: { [`${DOCKER_CODE_SERVER_PORT}/tcp`]: {}, }, HostConfig: { Binds: [ `${getDockerBindSourcePath(workspacePath)}:/home/coder/project`, `${getDockerBindSourcePath(runtimePaths.userDataPath)}:/home/coder/.local/share/code-server`, ], PortBindings: { [`${DOCKER_CODE_SERVER_PORT}/tcp`]: [{ HostIp: '127.0.0.1', HostPort: `${port}` }], }, }, Cmd: [ '--auth', 'none', '--bind-addr', `0.0.0.0:${DOCKER_CODE_SERVER_PORT}`, '--disable-telemetry', '--disable-update-check', '--user-data-dir', '/home/coder/.local/share/code-server', '/home/coder/project', ], }); await workspaceContainer.start(); log(`Docker workspace container '${containerName}' started from image '${imageName}'.`); const createdInspect = await workspaceContainer.inspect(); const createdEntry = registerDockerWorkspaceRuntime(config.id, createdInspect, socketPath); if (!createdEntry) { return { success: false, error: 'DOCKER_PORT_BINDING_MISSING' }; } const heartbeatError = await waitForWorkspaceHeartbeat({ port: createdEntry.port, log, getFailureReason: async () => { const info = await workspaceContainer.inspect(); return info.State.Running ? null : `DOCKER_CONTAINER_EXITED_${info.State.ExitCode}`; }, }); if (heartbeatError) { try { await workspaceContainer.remove({ force: true }); } catch { // Best-effort cleanup after failed boot. } dockerWorkspaces.delete(config.id); return { success: false, error: heartbeatError }; } return { success: true, containerId: createdEntry.containerId, port: createdEntry.port, runtime: 'docker', }; } /** * INTERNAL: Core provisioning logic with IDX support and auto-provisioning baseline. */ async function performProvisioning(config: WorkspaceConfig): Promise { const log = (msg: string) => { if (config.onLog) config.onLog(`[IDX:ENGINE] ${msg}`); provisioningBus.emit(`log:${config.id}`, msg); }; try { log(`Provisioning hermetic environment for '${config.projectName}'...`); // 0. HF PERSISTENCE: Restore profile from Dataset if available try { await HFStorage.syncFromDataset((msg) => log(msg)); } catch (e) { log(`[WARN] Persistent profile restoration failed: ${e instanceof Error ? e.message : String(e)}. Proceeding with clean environment.`); } // 1. Prepare Workspace Directory const runtimePaths = await resolveWorkspaceRuntimePaths(config); const workspacePath = ensureRuntimeWorkspacePath(runtimePaths, log); const userDataPath = runtimePaths.userDataPath; // 2. IDX Engine: Sync Environment const idxConfig = IdxEngine.getIdxConfig(workspacePath); log(`Declarative config detected (Packages: ${idxConfig.packages.length}). Initializing synchronization...`); await IdxEngine.syncNixEnvironment(workspacePath, idxConfig, (msg) => log(msg)); const flagPath = path.join(/*turbopackIgnore: true*/ workspacePath, '.idx-created'); if (!fs.existsSync(flagPath)) { if (idxConfig.onCreate) { log(`Executing onCreate lifecycle hook...`); await IdxEngine.runHook(workspacePath, 'onCreate', idxConfig.onCreate, (msg) => log(msg)); } fs.writeFileSync(flagPath, new Date().toISOString()); } const shouldPreferDockerRuntime = ENV_CONFIG.WORKSPACE_RUNTIME_PREFERENCE === 'docker' || (ENV_CONFIG.WORKSPACE_RUNTIME_PREFERENCE === 'auto' && process.platform === 'win32'); if (shouldPreferDockerRuntime) { log(`Evaluating Docker runtime for workspace boot...`); const dockerResult = await startDockerWorkspace(config, runtimePaths, workspacePath, log); if (dockerResult) { if (dockerResult.success && idxConfig.onStart) { log(`Executing background onStart lifecycle hooks...`); IdxEngine.runHook(workspacePath, 'onStart', idxConfig.onStart, (msg) => log(msg), true); } const finalDockerResult: WorkspaceOperationResult = { ...dockerResult, androidPort: config.withAndroidEmulator ? 6080 : undefined, }; if (dockerResult.success) { provisioningBus.emit(`ready:${config.id}`, finalDockerResult); } else { provisioningBus.emit(`error:${config.id}`, finalDockerResult); } return finalDockerResult; } if (ENV_CONFIG.WORKSPACE_RUNTIME_PREFERENCE === 'docker') { const dockerUnavailableResult: WorkspaceOperationResult = { success: false, error: 'DOCKER_DAEMON_UNREACHABLE', runtime: 'docker', }; provisioningBus.emit(`error:${config.id}`, dockerUnavailableResult); return dockerUnavailableResult; } log(`[INFO] Docker runtime unavailable. Falling back to native IDE process.`); } // 3. Spawn code-server natively const codeServerLaunch = resolveCodeServerLaunch(); const port = findAvailablePort(); const shellCommand = codeServerLaunch.command; const args = codeServerLaunch.args; const spawnCwd = (() => { try { return fs.realpathSync(workspacePath); } catch { return workspacePath; } })(); const baseArgs = [ '--auth', 'none', '--bind-addr', `127.0.0.1:${port}`, '--user-data-dir', userDataPath, '--disable-telemetry', '--disable-update-check', workspacePath, ]; const spawnEnv: NodeJS.ProcessEnv = { ...process.env, HOME: workspacePath, npm_config_cache: runtimePaths.npmCachePath, npm_config_update_notifier: 'false', }; delete spawnEnv.PORT; delete spawnEnv.SERVER_PORT; const launchArgs = [...args, ...baseArgs]; log(`IDE launch prepared. Binary: ${shellCommand} | cwd: ${spawnCwd} | target: ${workspacePath}`); let child: ChildProcess; try { child = spawn(shellCommand, launchArgs, { env: spawnEnv, cwd: spawnCwd, shell: false, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`IDE_SPAWN_CONFIGURATION_REJECTED: ${errorMessage}`); } log(`Spawning VS Code Orchestrator via ${codeServerLaunch.label} (PID: ${child.pid})...`); let childExited = false; let childFailureReason: string | null = null; child.on('error', (err) => { childFailureReason = `IDE_BINARY_FAILURE: ${err.message}`; log(`[FATAL] IDE binary failure: ${err.message}`); }); child.stdout?.on('data', (data) => { const out = data.toString().trim(); if (out.includes('listening on')) log(`[IDX:UP] ${out}`); else if (out.length > 0) log(`[IDE:CORE] ${out}`); }); child.stderr?.on('data', (data) => { const err = data.toString().trim(); if (err.length > 0) log(`[IDE:ERR] ${err}`); }); child.on('close', (code, signal) => { childExited = true; if (code !== 0 || signal) { childFailureReason = childFailureReason ?? `IDE_PROCESS_EXIT_${code ?? 'unknown'}${signal ? `_${signal}` : ''}`; } log(`[IDE:EXIT] IDE process died with code ${code} (Signal: ${signal})`); nativeProcesses.delete(config.id); }); nativeProcesses.set(config.id, { kind: 'native', pid: child.pid!, port, process: child }); const handshakeError = await waitForWorkspaceHeartbeat({ port, log, getFailureReason: async () => { if (!childExited) { return null; } const rawFailureMessage = childFailureReason ?? 'IDE_PROCESS_EXITED_BEFORE_HANDSHAKE'; return codeServerLaunch.usesNpx ? `${rawFailureMessage}. code-server could not be bootstrapped via npx. Install code-server globally or set CODE_SERVER_BIN.` : rawFailureMessage; }, }); if (handshakeError) { const entry = nativeProcesses.get(config.id); if (entry) { entry.process.kill(); nativeProcesses.delete(config.id); } const errResult = { success: false, error: handshakeError }; provisioningBus.emit(`error:${config.id}`, errResult); return errResult; } if (idxConfig.onStart) { log(`Executing background onStart lifecycle hooks...`); IdxEngine.runHook(workspacePath, 'onStart', idxConfig.onStart, (msg) => log(msg), true); } const finalResult: WorkspaceOperationResult = { success: true, containerId: `native-${config.id}`, androidPort: config.withAndroidEmulator ? 6080 : undefined, port, runtime: 'native', }; provisioningBus.emit(`ready:${config.id}`, finalResult); return finalResult; } catch (e) { const error = e instanceof Error ? e.message : String(e); log(`[FATAL] Provisioning pipeline collapsed: ${error}`); nativeProcesses.delete(config.id); dockerWorkspaces.delete(config.id); const errResult = { success: false, error: `PROVISIONING_FAILED: ${error}` }; provisioningBus.emit(`error:${config.id}`, errResult); return errResult; } } /** * Workspace provisioner with ATOMIC single-instance locking. */ export async function startWorkspaceContainer(config: WorkspaceConfig): Promise { const pendingWorkspace = pendingProvisioning.get(config.id); if (pendingWorkspace) { return await pendingWorkspace; } const existingRuntime = getWorkspaceRuntimeEntry(config.id); if (existingRuntime) { return { success: true, containerId: existingRuntime.kind === 'docker' ? existingRuntime.containerId : `native-${config.id}`, port: existingRuntime.port, runtime: existingRuntime.kind, }; } let pending = pendingProvisioning.get(config.id); if (!pending) { pending = performProvisioning(config).finally(() => { pendingProvisioning.delete(config.id); }); pendingProvisioning.set(config.id, pending); } return await pending; } /** * 🛠️ SELF-HEALING: Scans for running code-server instances to repopulate the proxy map. * This allows the IDE to survive server restarts or cold boots by probing active ports. */ export async function reconnectRunningWorkspaces() { const workspaceRoot = getWorkspaceRootPath(); const runtimeRootPath = getRuntimeRootPath(); console.log(`[BOOT] Probing filesystem segment: ${workspaceRoot} for existing sessions...`); try { const dockerResolution = await resolveDockerClient(ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS); if (dockerResolution) { const runningContainers = await dockerResolution.docker.listContainers({ filters: { label: [`${WORKSPACE_RUNTIME_LABEL}=${WORKSPACE_RUNTIME_LABEL_VALUE}`], }, }); for (const containerInfo of runningContainers) { const workspaceId = containerInfo.Labels?.[WORKSPACE_ID_LABEL]; const port = getPublishedPortForListEntry(containerInfo); if (!workspaceId || !port) { continue; } dockerWorkspaces.set(workspaceId, { kind: 'docker', containerId: containerInfo.Id, containerName: containerInfo.Names[0]?.replace(/^\//, '') ?? getWorkspaceContainerName(workspaceId), imageName: containerInfo.Image, port, socketPath: dockerResolution.socketPath, }); console.log(`[RECONNECT] Restored Docker workspace ${workspaceId} on port ${port}.`); } } } catch (error) { console.warn(`[RECONNECT:WARN] Docker workspace restore failed: ${error instanceof Error ? error.message : String(error)}`); } try { // Find all code-server processes // Note: Using a more robust ps grep that works across most POSIX environments const psCmd = process.platform === 'win32' ? 'tasklist' : "ps aux | grep code-server | grep -v grep"; const output = execSync(psCmd).toString(); const lines = output.split('\n'); for (const line of lines) { // Looking for: ... --bind-addr 127.0.0.1:8548 ... w/44c7597c const bindMatch = line.match(/--bind-addr 127\.0\.0\.1:(\d+)/); const userDataMatch = line.match(/\.codeverse-runtime[\/\\]([a-zA-Z0-9]{8})-userdata/); const runtimePathMatch = line.match(/\.codeverse-runtime[\/\\]([a-zA-Z0-9]{8})(?:\s|$)/); const legacyPathMatch = line.match(/[ /](?:w|workspaces)[\/\\]([a-zA-Z0-9]{8})/); if (bindMatch) { const shortId = userDataMatch?.[1] ?? runtimePathMatch?.[1] ?? legacyPathMatch?.[1]; if (!shortId) { continue; } const port = parseInt(bindMatch[1], 10); const metadataPath = path.join(runtimeRootPath, `${shortId}.id`); const legacyIdFile = path.join(workspaceRoot, shortId, '.codeverse-id'); let foundFullId = ""; if (fs.existsSync(metadataPath)) { foundFullId = fs.readFileSync(metadataPath, 'utf-8').trim(); } else if (fs.existsSync(legacyIdFile)) { foundFullId = fs.readFileSync(legacyIdFile, 'utf-8').trim(); } else { // Fallback: If no ID file, we use the shortId as the temporary key. foundFullId = shortId; console.warn(`[RECONNECT:WARN] No .codeverse-id for session ${shortId}. Using prefix mapping.`); } if (foundFullId && !nativeProcesses.has(foundFullId)) { // Capture PID reliably from ps output (column 2) const psParts = line.trim().split(/\s+/); const pid = parseInt(psParts[1]); console.log(`[RECONNECT] Identified active IDE ${foundFullId} (PID: ${pid}) on port ${port}. Restoration complete.`); nativeProcesses.set(foundFullId, { kind: 'native', pid, port, process: { pid, kill: () => { try { process.kill(pid, 'SIGKILL'); return true; } catch { try { execSync(`fuser -k ${port}/tcp`); } catch {} return true; } } } as WorkspaceProcess }); } } } } catch { // No processes found or ps failed } } /** * 🟢 ENGINE WATCHDOG: Background health monitor for native IDE processes. */ function startEngineWatchdog() { setInterval(async () => { for (const [id, entry] of nativeProcesses.entries()) { try { // 1. Zombie Check try { process.kill(entry.pid, 0); } catch { console.log(`[WATCHDOG] Process ${entry.pid} for ${id} is missing. Pruning.`); nativeProcesses.delete(id); continue; } // 2. Healthz Polling const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); try { const res = await fetch(`http://127.0.0.1:${entry.port}`, { signal: controller.signal }); if (!res.ok) throw new Error('Unhealthy'); } catch { console.warn(`[WATCHDOG] IDE ${id} (Port ${entry.port}) is non-responsive.`); // Optional: force restart if unhealthy for multiple cycles } finally { clearTimeout(timeoutId); } } catch (e) { console.error(`[WATCHDOG:ERR] ${e}`); } } }, 60000); } startEngineWatchdog(); async function stopDockerWorkspace(id: string): Promise { const dockerEntry = dockerWorkspaces.get(id); const docker = dockerEntry ? createDockerClient(dockerEntry.socketPath) : (await resolveDockerClient(ENV_CONFIG.DOCKER_PROBE_TIMEOUT_MS))?.docker; if (!docker) { dockerWorkspaces.delete(id); return false; } const existingContainerInfo = await resolveExistingWorkspaceContainer(docker, id); if (!existingContainerInfo) { dockerWorkspaces.delete(id); return false; } const container = docker.getContainer(existingContainerInfo.Id); try { const inspectInfo = await container.inspect(); if (inspectInfo.State.Running) { await container.stop({ t: 10 }); } } catch { // Container may already be stopped or missing. } try { await container.remove({ force: true }); } catch { // Container may already be removed. } dockerWorkspaces.delete(id); return true; } /** * Standardized stop method. */ export async function stopWorkspaceContainer(id: string): Promise<{ success: boolean }> { const nativeStopped = await stopNativeWorkspace(id); if (nativeStopped) { return { success: true }; } const dockerStopped = await stopDockerWorkspace(id); return { success: dockerStopped }; } /** * Modern Docker Manager class. */ export class DockerManager { async getContainerStatus(id: string): Promise<"running" | "stopped" | "not_found"> { if (isWorkspaceRunning(id)) return "running"; return "stopped"; } async stopContainer(id: string): Promise { return (await stopWorkspaceContainer(id)).success; } async startWorkspace(config: WorkspaceConfig): Promise { const result = await startWorkspaceContainer(config); return result.success; } } export const dockerManager = new DockerManager();