Files
2026-02-22 01:11:52 +01:00

145 lines
5.0 KiB
TypeScript

import { spawn, ChildProcess } from "child_process"
import * as crypto from "crypto"
import * as fs from "fs"
import * as path from "path"
import * as vscode from "vscode"
import { parseServerPort } from "./server-utils"
export interface ServerInstance {
port: number
password: string
process: ChildProcess
}
export class ServerManager {
private instance: ServerInstance | null = null
private startupPromise: Promise<ServerInstance> | null = null
constructor(private readonly context: vscode.ExtensionContext) {}
/**
* Get or start the server instance
*/
async getServer(): Promise<ServerInstance> {
console.log("[Kilo New] ServerManager: 🔍 getServer called")
if (this.instance) {
console.log("[Kilo New] ServerManager: ♻️ Returning existing instance:", { port: this.instance.port })
return this.instance
}
if (this.startupPromise) {
console.log("[Kilo New] ServerManager: ⏳ Startup already in progress, waiting...")
return this.startupPromise
}
console.log("[Kilo New] ServerManager: 🚀 Starting new server instance...")
this.startupPromise = this.startServer()
try {
this.instance = await this.startupPromise
console.log("[Kilo New] ServerManager: ✅ Server started successfully:", { port: this.instance.port })
return this.instance
} finally {
this.startupPromise = null
}
}
private async startServer(): Promise<ServerInstance> {
const password = crypto.randomBytes(32).toString("hex")
const cliPath = this.getCliPath()
console.log("[Kilo New] ServerManager: 📍 CLI path:", cliPath)
console.log("[Kilo New] ServerManager: 🔐 Generated password (length):", password.length)
// Verify the CLI binary exists
if (!fs.existsSync(cliPath)) {
throw new Error(
`CLI binary not found at expected path: ${cliPath}. Please ensure the CLI is built and bundled with the extension.`,
)
}
const stat = fs.statSync(cliPath)
console.log("[Kilo New] ServerManager: 📄 CLI isFile:", stat.isFile())
console.log("[Kilo New] ServerManager: 📄 CLI mode (octal):", (stat.mode & 0o777).toString(8))
return new Promise((resolve, reject) => {
console.log("[Kilo New] ServerManager: 🎬 Spawning CLI process:", cliPath, ["serve", "--port", "0"])
const serverProcess = spawn(cliPath, ["serve", "--port", "0"], {
env: {
...process.env,
KILO_SERVER_PASSWORD: password,
KILO_CLIENT: "vscode",
KILOCODE_FEATURE: "vscode-extension", // kilocode_change - feature tracking
KILO_TELEMETRY_LEVEL: vscode.env.isTelemetryEnabled ? "all" : "off",
KILO_APP_NAME: "kilo-code",
KILO_EDITOR_NAME: vscode.env.appName,
KILO_PLATFORM: "vscode",
KILO_MACHINE_ID: vscode.env.machineId,
KILO_APP_VERSION: this.context.extension.packageJSON.version,
KILO_VSCODE_VERSION: vscode.version,
},
stdio: ["ignore", "pipe", "pipe"],
})
console.log("[Kilo New] ServerManager: 📦 Process spawned with PID:", serverProcess.pid)
let resolved = false
serverProcess.stdout?.on("data", (data: Buffer) => {
const output = data.toString()
console.log("[Kilo New] ServerManager: 📥 CLI Server stdout:", output)
const port = parseServerPort(output)
if (port !== null && !resolved) {
resolved = true
console.log("[Kilo New] ServerManager: 🎯 Port detected:", port)
resolve({ port, password, process: serverProcess })
}
})
serverProcess.stderr?.on("data", (data: Buffer) => {
const errorOutput = data.toString()
console.error("[Kilo New] ServerManager: ⚠️ CLI Server stderr:", errorOutput)
})
serverProcess.on("error", (error) => {
console.error("[Kilo New] ServerManager: ❌ Process error:", error)
if (!resolved) {
reject(error)
}
})
serverProcess.on("exit", (code) => {
console.log("[Kilo New] ServerManager: 🛑 Process exited with code:", code)
if (this.instance?.process === serverProcess) {
this.instance = null
}
if (!resolved) {
reject(new Error(`CLI process exited with code ${code} before server started`))
}
})
// Timeout after 30 seconds
setTimeout(() => {
if (!resolved) {
console.error("[Kilo New] ServerManager: ⏰ Server startup timeout (30s)")
serverProcess.kill()
reject(new Error("Server startup timeout"))
}
}, 30000)
})
}
private getCliPath(): string {
// Always use the bundled binary from the extension directory
const binName = process.platform === "win32" ? "kilo.exe" : "kilo" // kilocode_change
const cliPath = path.join(this.context.extensionPath, "bin", binName)
console.log("[Kilo New] ServerManager: 📦 Using CLI path:", cliPath)
return cliPath
}
dispose(): void {
if (this.instance) {
this.instance.process.kill("SIGTERM")
this.instance = null
}
}
}