Przeglądaj źródła

experimental vscode impls & build-proto cleanup (#4493)

* experimental vscode impls

* weird errors

* add workspace host config

* format

---------

Co-authored-by: Andrei Eternal <[email protected]>
Andrei Eternal 6 miesięcy temu
rodzic
commit
f00c5f4ecc

+ 29 - 0
proto/build-proto-config.js

@@ -0,0 +1,29 @@
+// Configuration file for protocol buffer build scripts
+// Contains service name mappings used by both build-proto.js and build-go-proto.js
+
+// List of gRPC services
+// To add a new service, simply add it to this map and run the build scripts
+// The service handler will be automatically discovered and used by grpc-handler.ts
+export const serviceNameMap = {
+	account: "cline.AccountService",
+	browser: "cline.BrowserService",
+	checkpoints: "cline.CheckpointsService",
+	file: "cline.FileService",
+	mcp: "cline.McpService",
+	state: "cline.StateService",
+	task: "cline.TaskService",
+	web: "cline.WebService",
+	models: "cline.ModelsService",
+	slash: "cline.SlashService",
+	ui: "cline.UiService",
+	// Add new services here - no other code changes needed!
+}
+
+// List of host gRPC services (IDE API bridge)
+// These services are implemented in the IDE extension and called by the standalone Cline Core
+export const hostServiceNameMap = {
+	uri: "host.UriService",
+	watch: "host.WatchService",
+	workspace: "host.WorkspaceService",
+	// Add new host services here
+}

+ 19 - 46
proto/build-proto.js

@@ -9,16 +9,18 @@ import chalk from "chalk"
 import os from "os"
 
 import { createRequire } from "module"
+import { serviceNameMap, hostServiceNameMap } from "./build-proto-config.js"
+
 const require = createRequire(import.meta.url)
 const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc")
 
 const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url))
 const ROOT_DIR = path.resolve(SCRIPT_DIR, "..")
 
-const TS_OUT_DIR = path.join(ROOT_DIR, "src/shared/proto")
-const GRPC_JS_OUT_DIR = path.join(ROOT_DIR, "src/generated/grpc-js")
-const NICE_JS_OUT_DIR = path.join(ROOT_DIR, "src/generated/nice-grpc")
-const DESCRIPTOR_OUT_DIR = path.join(ROOT_DIR, "dist-standalone/proto")
+const TS_OUT_DIR = path.join(ROOT_DIR, "src", "shared", "proto")
+const GRPC_JS_OUT_DIR = path.join(ROOT_DIR, "src", "generated", "grpc-js")
+const NICE_JS_OUT_DIR = path.join(ROOT_DIR, "src", "generated", "nice-grpc")
+const DESCRIPTOR_OUT_DIR = path.join(ROOT_DIR, "dist-standalone", "proto")
 
 const isWindows = process.platform === "win32"
 const TS_PROTO_PLUGIN = isWindows
@@ -34,34 +36,13 @@ const TS_PROTO_OPTIONS = [
 	"useDate=false", // Timestamp fields will not be automatically converted to Date.
 ]
 
-// List of gRPC services
-// To add a new service, simply add it to this map and run this script
-// The service handler will be automatically discovered and used by grpc-handler.ts
-const serviceNameMap = {
-	account: "cline.AccountService",
-	browser: "cline.BrowserService",
-	checkpoints: "cline.CheckpointsService",
-	file: "cline.FileService",
-	mcp: "cline.McpService",
-	state: "cline.StateService",
-	task: "cline.TaskService",
-	web: "cline.WebService",
-	models: "cline.ModelsService",
-	slash: "cline.SlashService",
-	ui: "cline.UiService",
-	// Add new services here - no other code changes needed!
-}
-const serviceDirs = Object.keys(serviceNameMap).map((serviceKey) => path.join(ROOT_DIR, "src/core/controller", serviceKey))
-
-// List of host gRPC services (IDE API bridge)
-// These services are implemented in the IDE extension and called by the standalone Cline Core
-const hostServiceNameMap = {
-	uri: "host.UriService",
-	watch: "host.WatchService",
-	workspace: "host.WorkspaceService",
-	// Add new host services here
-}
-const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) => path.join(ROOT_DIR, "src/hosts/vscode", serviceKey))
+// Service directories derived from imported serviceNameMap
+const serviceDirs = Object.keys(serviceNameMap).map((serviceKey) => path.join(ROOT_DIR, "src", "core", "controller", serviceKey))
+
+// Host service directories derived from imported hostServiceNameMap
+const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) =>
+	path.join(ROOT_DIR, "src", "hosts", "vscode", serviceKey),
+)
 
 async function main() {
 	console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
@@ -177,7 +158,7 @@ export {
 	${serviceExports.join(",\n\t")}
 }`
 
-	const filePath = path.join(ROOT_DIR, "webview-ui/src/services/grpc-client.ts")
+	const filePath = path.join(ROOT_DIR, "webview-ui", "src", "services", "grpc-client.ts")
 	await writeFileWithMkdirs(filePath, content)
 	log_verbose(chalk.green(`Generated gRPC client at ${filePath}`))
 }
@@ -386,7 +367,7 @@ export interface ServiceHandlerConfig {
 export const serviceHandlers: Record<string, ServiceHandlerConfig> = {${serviceConfigs.join(",")}
 };`
 
-	const configPath = path.join(ROOT_DIR, "src/core/controller/grpc-service-config.ts")
+	const configPath = path.join(ROOT_DIR, "src", "core", "controller", "grpc-service-config.ts")
 	await writeFileWithMkdirs(configPath, content)
 	log_verbose(chalk.green(`Generated service configuration at ${configPath}`))
 }
@@ -602,13 +583,12 @@ async function cleanup() {
 	for (const file of existingFiles) {
 		await fs.unlink(path.join(TS_OUT_DIR, file))
 	}
-	await rmdir(path.join(ROOT_DIR, "src/generated"))
+	await rmdir(path.join(ROOT_DIR, "src", "generated"))
 
 	// Clean up generated files that were moved.
-	await fs.rm(path.join(ROOT_DIR, "src/standalone/services/host-grpc-client.ts"), { force: true })
-	await rmdir(path.join(ROOT_DIR, "src/standalone/services"))
-
-	await fs.rm(path.join(ROOT_DIR, "hosts/vscode"), { force: true, recursive: true })
+	await fs.rm(path.join(ROOT_DIR, "src", "standalone", "services", "host-grpc-client.ts"), { force: true })
+	await rmdir(path.join(ROOT_DIR, "src", "standalone", "services"))
+	await fs.rm(path.join(ROOT_DIR, "hosts", "vscode"), { force: true, recursive: true })
 	await rmdir(path.join(ROOT_DIR, "hosts"))
 
 	await fs.rm(path.join(ROOT_DIR, "src/standalone/server-setup.ts"), { force: true })
@@ -636,13 +616,6 @@ async function rmdir(path) {
 	}
 }
 
-function serviceNameWithoutPackage(fullServiceName) {
-	return fullServiceName.replace(/.*\./, "")
-}
-function lowercaseFirstChar(str) {
-	return str.charAt(0).toLowerCase() + str.slice(1)
-}
-
 // Check for Apple Silicon compatibility
 function checkAppleSiliconCompatibility() {
 	// Only run check on macOS

+ 514 - 0
standalone/runtime-files/vscode/enhanced-terminal.js

@@ -0,0 +1,514 @@
+const { spawn } = require("child_process")
+const { EventEmitter } = require("events")
+const path = require("path")
+const os = require("os")
+
+// Enhanced terminal management for standalone Cline
+// This replaces VSCode's terminal integration with real subprocess management
+
+class StandaloneTerminalProcess extends EventEmitter {
+	constructor() {
+		super()
+		this.waitForShellIntegration = false // We don't need to wait since we control the process
+		this.isListening = true
+		this.buffer = ""
+		this.fullOutput = ""
+		this.lastRetrievedIndex = 0
+		this.isHot = false
+		this.hotTimer = null
+		this.childProcess = null
+		this.exitCode = null
+		this.isCompleted = false
+	}
+
+	async run(terminal, command) {
+		console.log(`[StandaloneTerminal] Running command: ${command}`)
+
+		// Get shell and working directory from terminal
+		const shell = terminal._shellPath || this.getDefaultShell()
+		const cwd = terminal._cwd || process.cwd()
+
+		// Prepare command for execution
+		const shellArgs = this.getShellArgs(shell, command)
+
+		try {
+			// Spawn the process
+			this.childProcess = spawn(shell, shellArgs, {
+				cwd: cwd,
+				stdio: ["pipe", "pipe", "pipe"],
+				env: { ...process.env, TERM: "xterm-256color" },
+			})
+
+			// Track process state
+			let didEmitEmptyLine = false
+
+			// Handle stdout
+			this.childProcess.stdout.on("data", (data) => {
+				const output = data.toString()
+				this.handleOutput(output, didEmitEmptyLine)
+				if (!didEmitEmptyLine && output) {
+					this.emit("line", "") // Signal start of output
+					didEmitEmptyLine = true
+				}
+			})
+
+			// Handle stderr
+			this.childProcess.stderr.on("data", (data) => {
+				const output = data.toString()
+				this.handleOutput(output, didEmitEmptyLine)
+				if (!didEmitEmptyLine && output) {
+					this.emit("line", "")
+					didEmitEmptyLine = true
+				}
+			})
+
+			// Handle process completion
+			this.childProcess.on("close", (code, signal) => {
+				console.log(`[StandaloneTerminal] Process closed with code ${code}, signal ${signal}`)
+				this.exitCode = code
+				this.isCompleted = true
+				this.emitRemainingBuffer()
+
+				// Clear hot timer
+				if (this.hotTimer) {
+					clearTimeout(this.hotTimer)
+					this.isHot = false
+				}
+
+				this.emit("completed")
+				this.emit("continue")
+			})
+
+			// Handle process errors
+			this.childProcess.on("error", (error) => {
+				console.error(`[StandaloneTerminal] Process error:`, error)
+				this.emit("error", error)
+			})
+
+			// Update terminal's process reference
+			terminal._process = this.childProcess
+			terminal._processId = this.childProcess.pid
+		} catch (error) {
+			console.error(`[StandaloneTerminal] Failed to spawn process:`, error)
+			this.emit("error", error)
+		}
+	}
+
+	handleOutput(data, didEmitEmptyLine) {
+		// Set process as hot (actively outputting)
+		this.isHot = true
+		if (this.hotTimer) {
+			clearTimeout(this.hotTimer)
+		}
+
+		// Check for compilation markers to adjust hot timeout
+		const compilingMarkers = ["compiling", "building", "bundling", "transpiling", "generating", "starting"]
+		const markerNullifiers = [
+			"compiled",
+			"success",
+			"finish",
+			"complete",
+			"succeed",
+			"done",
+			"end",
+			"stop",
+			"exit",
+			"terminate",
+			"error",
+			"fail",
+		]
+
+		const isCompiling =
+			compilingMarkers.some((marker) => data.toLowerCase().includes(marker.toLowerCase())) &&
+			!markerNullifiers.some((nullifier) => data.toLowerCase().includes(nullifier.toLowerCase()))
+
+		const hotTimeout = isCompiling ? 15000 : 2000
+		this.hotTimer = setTimeout(() => {
+			this.isHot = false
+		}, hotTimeout)
+
+		// Store full output
+		this.fullOutput += data
+
+		if (this.isListening) {
+			this.emitLines(data)
+			this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length
+		}
+	}
+
+	emitLines(chunk) {
+		this.buffer += chunk
+		let lineEndIndex
+		while ((lineEndIndex = this.buffer.indexOf("\n")) !== -1) {
+			let line = this.buffer.slice(0, lineEndIndex).trimEnd()
+			this.emit("line", line)
+			this.buffer = this.buffer.slice(lineEndIndex + 1)
+		}
+	}
+
+	emitRemainingBuffer() {
+		if (this.buffer && this.isListening) {
+			const remainingBuffer = this.removeLastLineArtifacts(this.buffer)
+			if (remainingBuffer) {
+				this.emit("line", remainingBuffer)
+			}
+			this.buffer = ""
+			this.lastRetrievedIndex = this.fullOutput.length
+		}
+	}
+
+	continue() {
+		this.emitRemainingBuffer()
+		this.isListening = false
+		this.removeAllListeners("line")
+		this.emit("continue")
+	}
+
+	getUnretrievedOutput() {
+		const unretrieved = this.fullOutput.slice(this.lastRetrievedIndex)
+		this.lastRetrievedIndex = this.fullOutput.length
+		return this.removeLastLineArtifacts(unretrieved)
+	}
+
+	removeLastLineArtifacts(output) {
+		const lines = output.trimEnd().split("\n")
+		if (lines.length > 0) {
+			const lastLine = lines[lines.length - 1]
+			lines[lines.length - 1] = lastLine.replace(/[%$#>]\s*$/, "")
+		}
+		return lines.join("\n").trimEnd()
+	}
+
+	getDefaultShell() {
+		if (process.platform === "win32") {
+			return process.env.COMSPEC || "cmd.exe"
+		} else {
+			return process.env.SHELL || "/bin/bash"
+		}
+	}
+
+	getShellArgs(shell, command) {
+		if (process.platform === "win32") {
+			if (shell.toLowerCase().includes("powershell") || shell.toLowerCase().includes("pwsh")) {
+				return ["-Command", command]
+			} else {
+				return ["/c", command]
+			}
+		} else {
+			return ["-c", command]
+		}
+	}
+
+	// Terminate the process if it's still running
+	terminate() {
+		if (this.childProcess && !this.isCompleted) {
+			console.log(`[StandaloneTerminal] Terminating process ${this.childProcess.pid}`)
+			this.childProcess.kill("SIGTERM")
+
+			// Force kill after timeout
+			setTimeout(() => {
+				if (!this.isCompleted) {
+					console.log(`[StandaloneTerminal] Force killing process ${this.childProcess.pid}`)
+					this.childProcess.kill("SIGKILL")
+				}
+			}, 5000)
+		}
+	}
+}
+
+class StandaloneTerminal {
+	constructor(options = {}) {
+		this.name = options.name || `Terminal ${Math.floor(Math.random() * 10000)}`
+		this.processId = Promise.resolve(Math.floor(Math.random() * 100000))
+		this.creationOptions = options
+		this.exitStatus = undefined
+		this.state = { isInteractedWith: false }
+		this._cwd = options.cwd || process.cwd()
+		this._shellPath = options.shellPath
+		this._process = null
+		this._processId = null
+
+		// Mock shell integration for compatibility
+		this.shellIntegration = {
+			cwd: { fsPath: this._cwd },
+			executeCommand: (command) => {
+				// Return a mock execution object that the TerminalProcess expects
+				return {
+					read: async function* () {
+						// This will be handled by our StandaloneTerminalProcess
+						yield ""
+					},
+				}
+			},
+		}
+
+		console.log(`[StandaloneTerminal] Created terminal: ${this.name} in ${this._cwd}`)
+	}
+
+	sendText(text, addNewLine = true) {
+		console.log(`[StandaloneTerminal] sendText: ${text}`)
+
+		// If we have an active process, send input to it
+		if (this._process && !this._process.killed) {
+			try {
+				this._process.stdin.write(text + (addNewLine ? "\n" : ""))
+			} catch (error) {
+				console.error(`[StandaloneTerminal] Error sending text to process:`, error)
+			}
+		} else {
+			// For compatibility with old behavior, we could spawn a new process
+			console.log(`[StandaloneTerminal] No active process to send text to`)
+		}
+	}
+
+	show() {
+		console.log(`[StandaloneTerminal] show: ${this.name}`)
+		this.state.isInteractedWith = true
+	}
+
+	hide() {
+		console.log(`[StandaloneTerminal] hide: ${this.name}`)
+	}
+
+	dispose() {
+		console.log(`[StandaloneTerminal] dispose: ${this.name}`)
+		if (this._process && !this._process.killed) {
+			this._process.kill("SIGTERM")
+		}
+	}
+}
+
+// Terminal registry for tracking terminals
+class StandaloneTerminalRegistry {
+	constructor() {
+		this.terminals = new Map()
+		this.nextId = 1
+	}
+
+	createTerminal(options = {}) {
+		const terminal = new StandaloneTerminal(options)
+		const id = this.nextId++
+
+		const terminalInfo = {
+			id: id,
+			terminal: terminal,
+			busy: false,
+			lastCommand: "",
+			shellPath: options.shellPath,
+			lastActive: Date.now(),
+			pendingCwdChange: undefined,
+			cwdResolved: undefined,
+		}
+
+		this.terminals.set(id, terminalInfo)
+		console.log(`[StandaloneTerminalRegistry] Created terminal ${id}`)
+		return terminalInfo
+	}
+
+	getTerminal(id) {
+		return this.terminals.get(id)
+	}
+
+	getAllTerminals() {
+		return Array.from(this.terminals.values())
+	}
+
+	removeTerminal(id) {
+		const terminalInfo = this.terminals.get(id)
+		if (terminalInfo) {
+			terminalInfo.terminal.dispose()
+			this.terminals.delete(id)
+			console.log(`[StandaloneTerminalRegistry] Removed terminal ${id}`)
+		}
+	}
+
+	updateTerminal(id, updates) {
+		const terminalInfo = this.terminals.get(id)
+		if (terminalInfo) {
+			Object.assign(terminalInfo, updates)
+		}
+	}
+}
+
+// Enhanced terminal manager
+class StandaloneTerminalManager {
+	constructor() {
+		this.registry = new StandaloneTerminalRegistry()
+		this.processes = new Map()
+		this.terminalIds = new Set()
+		this.shellIntegrationTimeout = 4000
+		this.terminalReuseEnabled = true
+		this.terminalOutputLineLimit = 500
+		this.defaultTerminalProfile = "default"
+	}
+
+	runCommand(terminalInfo, command) {
+		console.log(`[StandaloneTerminalManager] Running command on terminal ${terminalInfo.id}: ${command}`)
+
+		terminalInfo.busy = true
+		terminalInfo.lastCommand = command
+
+		const process = new StandaloneTerminalProcess()
+		this.processes.set(terminalInfo.id, process)
+
+		process.once("completed", () => {
+			terminalInfo.busy = false
+			console.log(`[StandaloneTerminalManager] Command completed on terminal ${terminalInfo.id}`)
+		})
+
+		process.once("error", (error) => {
+			terminalInfo.busy = false
+			console.error(`[StandaloneTerminalManager] Command error on terminal ${terminalInfo.id}:`, error)
+		})
+
+		// Create promise for the process
+		const promise = new Promise((resolve, reject) => {
+			process.once("continue", () => resolve())
+			process.once("error", (error) => reject(error))
+		})
+
+		// Run the command immediately (no shell integration wait needed)
+		process.run(terminalInfo.terminal, command)
+
+		// Return merged promise/process object
+		return this.mergePromise(process, promise)
+	}
+
+	async getOrCreateTerminal(cwd) {
+		const terminals = this.registry.getAllTerminals()
+
+		// Find available terminal with matching CWD
+		const matchingTerminal = terminals.find((t) => {
+			if (t.busy) return false
+			return t.terminal._cwd === cwd
+		})
+
+		if (matchingTerminal) {
+			this.terminalIds.add(matchingTerminal.id)
+			console.log(`[StandaloneTerminalManager] Reusing terminal ${matchingTerminal.id}`)
+			return matchingTerminal
+		}
+
+		// Find any available terminal if reuse is enabled
+		if (this.terminalReuseEnabled) {
+			const availableTerminal = terminals.find((t) => !t.busy)
+			if (availableTerminal) {
+				// Change directory
+				await this.runCommand(availableTerminal, `cd "${cwd}"`)
+				availableTerminal.terminal._cwd = cwd
+				availableTerminal.terminal.shellIntegration.cwd.fsPath = cwd
+				this.terminalIds.add(availableTerminal.id)
+				console.log(`[StandaloneTerminalManager] Reused terminal ${availableTerminal.id} with cd`)
+				return availableTerminal
+			}
+		}
+
+		// Create new terminal
+		const newTerminalInfo = this.registry.createTerminal({
+			cwd: cwd,
+			name: `Cline Terminal ${this.registry.nextId}`,
+		})
+		this.terminalIds.add(newTerminalInfo.id)
+		console.log(`[StandaloneTerminalManager] Created new terminal ${newTerminalInfo.id}`)
+		return newTerminalInfo
+	}
+
+	getTerminals(busy) {
+		return Array.from(this.terminalIds)
+			.map((id) => this.registry.getTerminal(id))
+			.filter((t) => t && t.busy === busy)
+			.map((t) => ({ id: t.id, lastCommand: t.lastCommand }))
+	}
+
+	getUnretrievedOutput(terminalId) {
+		if (!this.terminalIds.has(terminalId)) {
+			return ""
+		}
+		const process = this.processes.get(terminalId)
+		return process ? process.getUnretrievedOutput() : ""
+	}
+
+	isProcessHot(terminalId) {
+		const process = this.processes.get(terminalId)
+		return process ? process.isHot : false
+	}
+
+	processOutput(outputLines) {
+		if (outputLines.length > this.terminalOutputLineLimit) {
+			const halfLimit = Math.floor(this.terminalOutputLineLimit / 2)
+			const start = outputLines.slice(0, halfLimit)
+			const end = outputLines.slice(outputLines.length - halfLimit)
+			return `${start.join("\n")}\n... (output truncated) ...\n${end.join("\n")}`.trim()
+		}
+		return outputLines.join("\n").trim()
+	}
+
+	disposeAll() {
+		// Terminate all processes
+		for (const [terminalId, process] of this.processes) {
+			if (process && process.terminate) {
+				process.terminate()
+			}
+		}
+
+		// Clear all tracking
+		this.terminalIds.clear()
+		this.processes.clear()
+
+		// Dispose all terminals
+		for (const terminalInfo of this.registry.getAllTerminals()) {
+			terminalInfo.terminal.dispose()
+		}
+
+		console.log(`[StandaloneTerminalManager] Disposed all terminals`)
+	}
+
+	// Set shell integration timeout (compatibility method)
+	setShellIntegrationTimeout(timeout) {
+		this.shellIntegrationTimeout = timeout
+		console.log(`[StandaloneTerminalManager] Set shell integration timeout to ${timeout}ms`)
+	}
+
+	// Set terminal reuse enabled (compatibility method)
+	setTerminalReuseEnabled(enabled) {
+		this.terminalReuseEnabled = enabled
+		console.log(`[StandaloneTerminalManager] Set terminal reuse enabled to ${enabled}`)
+	}
+
+	// Set terminal output line limit (compatibility method)
+	setTerminalOutputLineLimit(limit) {
+		this.terminalOutputLineLimit = limit
+		console.log(`[StandaloneTerminalManager] Set terminal output line limit to ${limit}`)
+	}
+
+	// Set default terminal profile (compatibility method)
+	setDefaultTerminalProfile(profile) {
+		this.defaultTerminalProfile = profile
+		console.log(`[StandaloneTerminalManager] Set default terminal profile to ${profile}`)
+	}
+
+	// Helper to merge process and promise (similar to execa)
+	mergePromise(process, promise) {
+		const nativePromisePrototype = (async () => {})().constructor.prototype
+		const descriptors = ["then", "catch", "finally"].map((property) => [
+			property,
+			Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property),
+		])
+
+		for (const [property, descriptor] of descriptors) {
+			if (descriptor) {
+				const value = descriptor.value.bind(promise)
+				Reflect.defineProperty(process, property, { ...descriptor, value })
+			}
+		}
+
+		return process
+	}
+}
+
+module.exports = {
+	StandaloneTerminal,
+	StandaloneTerminalProcess,
+	StandaloneTerminalRegistry,
+	StandaloneTerminalManager,
+}

+ 718 - 23
standalone/runtime-files/vscode/vscode-impls.js

@@ -2,8 +2,19 @@ console.log("Loading stub impls...")
 
 const { createStub } = require("./stub-utils")
 const open = require("open").default
+const fs = require("fs")
+const path = require("path")
+const { StandaloneTerminalManager } = require("./enhanced-terminal")
 
+// Import the base vscode object from stubs
+const vscode = require("./vscode-stubs.js")
+
+// Create global terminal manager instance
+const globalTerminalManager = new StandaloneTerminalManager()
+
+// Extend the existing window object from stubs rather than overwriting it
 vscode.window = {
+	...vscode.window, // Keep existing properties from stubs
 	showInformationMessage: (...args) => {
 		console.log("Stubbed showInformationMessage:", ...args)
 		return Promise.resolve(undefined)
@@ -28,9 +39,210 @@ vscode.window = {
 		console.log("Stubbed showSaveDialog:", options)
 		return undefined
 	},
-	showTextDocument: async (...args) => {
-		console.log("Stubbed showTextDocument:", ...args)
-		return {}
+	showTextDocument: async (uri, options) => {
+		console.log("Stubbed showTextDocument:", uri, options)
+
+		// Extract file path from URI
+		let filePath = uri.path || uri.fsPath || uri
+		if (typeof filePath !== "string") {
+			filePath = uri.toString()
+		}
+
+		// Remove file:// prefix if present
+		if (filePath.startsWith("file://")) {
+			filePath = filePath.substring(7)
+		}
+
+		// Create a function that always reads the current file content
+		const getCurrentFileContent = async () => {
+			try {
+				const content = await fs.promises.readFile(filePath, "utf8")
+				console.log(`getCurrentFileContent: Read file ${filePath} (${content.length} chars)`)
+				return content
+			} catch (error) {
+				console.log(`getCurrentFileContent: Could not read file ${filePath}:`, error.message)
+				return ""
+			}
+		}
+
+		// Try to read the initial file content
+		let fileContent = await getCurrentFileContent()
+		let lineCount = fileContent.split("\n").length
+
+		// Check if we already have an active editor for this file path
+		const existingEditor = vscode.window._documentEditors && vscode.window._documentEditors[filePath]
+		if (existingEditor) {
+			console.log(`showTextDocument: Updating existing editor for ${filePath}`)
+			// Update the existing editor's content
+			fileContent = await getCurrentFileContent()
+			lineCount = fileContent.split("\n").length
+
+			// Update the document's getText method to return current content
+			existingEditor.document.getText = (range) => {
+				// Always read fresh content for getText calls
+				const currentContent = require("fs").readFileSync(filePath, "utf8")
+				if (!range) {
+					return currentContent
+				}
+				// Handle range-based getText with current content
+				const lines = currentContent.split("\n")
+				const startLine = Math.max(0, range.start.line)
+				const endLine = Math.min(lines.length - 1, range.end.line)
+
+				if (startLine === endLine) {
+					// Single line
+					const line = lines[startLine] || ""
+					const startChar = Math.max(0, range.start.character)
+					const endChar = Math.min(line.length, range.end.character)
+					return line.substring(startChar, endChar)
+				} else {
+					// Multiple lines
+					const result = []
+					for (let i = startLine; i <= endLine; i++) {
+						const line = lines[i] || ""
+						if (i === startLine) {
+							result.push(line.substring(range.start.character))
+						} else if (i === endLine) {
+							result.push(line.substring(0, range.end.character))
+						} else {
+							result.push(line)
+						}
+					}
+					return result.join("\n")
+				}
+			}
+
+			// Update other properties
+			existingEditor.document.lineCount = lineCount
+			existingEditor.document.fileName = filePath
+
+			// Update the active text editor reference
+			vscode.window.activeTextEditor = existingEditor
+
+			return existingEditor
+		}
+
+		// Create a new mock text editor that always reads current file content
+		const mockEditor = {
+			document: {
+				uri: uri,
+				fileName: filePath,
+				isDirty: false,
+				lineCount: lineCount,
+				getText: (range) => {
+					// Always read fresh content for getText calls
+					try {
+						const currentContent = require("fs").readFileSync(filePath, "utf8")
+						console.log(`document.getText: Read fresh content (${currentContent.length} chars)`)
+						if (!range) {
+							return currentContent
+						}
+						// Handle range-based getText with current content
+						const lines = currentContent.split("\n")
+						const startLine = Math.max(0, range.start.line)
+						const endLine = Math.min(lines.length - 1, range.end.line)
+
+						if (startLine === endLine) {
+							// Single line
+							const line = lines[startLine] || ""
+							const startChar = Math.max(0, range.start.character)
+							const endChar = Math.min(line.length, range.end.character)
+							return line.substring(startChar, endChar)
+						} else {
+							// Multiple lines
+							const result = []
+							for (let i = startLine; i <= endLine; i++) {
+								const line = lines[i] || ""
+								if (i === startLine) {
+									result.push(line.substring(range.start.character))
+								} else if (i === endLine) {
+									result.push(line.substring(0, range.end.character))
+								} else {
+									result.push(line)
+								}
+							}
+							return result.join("\n")
+						}
+					} catch (error) {
+						console.error(`Error reading file in getText: ${error.message}`)
+						return ""
+					}
+				},
+				save: async () => {
+					console.log("Called mock textDocument.save")
+					return true
+				},
+				positionAt: (offset) => {
+					try {
+						const currentContent = require("fs").readFileSync(filePath, "utf8")
+						const lines = currentContent.split("\n")
+						let currentOffset = 0
+						for (let line = 0; line < lines.length; line++) {
+							const lineLength = lines[line].length + 1 // +1 for newline
+							if (currentOffset + lineLength > offset) {
+								return { line: line, character: offset - currentOffset }
+							}
+							currentOffset += lineLength
+						}
+						return { line: lines.length - 1, character: lines[lines.length - 1]?.length || 0 }
+					} catch (error) {
+						return { line: 0, character: 0 }
+					}
+				},
+				offsetAt: (position) => {
+					try {
+						const currentContent = require("fs").readFileSync(filePath, "utf8")
+						const lines = currentContent.split("\n")
+						let offset = 0
+						for (let i = 0; i < position.line && i < lines.length; i++) {
+							offset += lines[i].length + 1 // +1 for newline
+						}
+						offset += Math.min(position.character, lines[position.line]?.length || 0)
+						return offset
+					} catch (error) {
+						return 0
+					}
+				},
+			},
+			selection: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
+			selections: [],
+			visibleRanges: [{ start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }],
+			options: {},
+			viewColumn: 1,
+			edit: async (callback) => {
+				console.log("Called mock textEditor.edit")
+				return true
+			},
+			insertSnippet: async () => true,
+			setDecorations: () => {},
+			revealRange: () => {},
+			show: () => {},
+			hide: () => {},
+		}
+
+		// Store the editor by file path for future reference
+		if (!vscode.window._documentEditors) {
+			vscode.window._documentEditors = {}
+		}
+		vscode.window._documentEditors[filePath] = mockEditor
+
+		// Update the active text editor
+		vscode.window.activeTextEditor = mockEditor
+
+		// Trigger onDidChangeActiveTextEditor listeners
+		if (vscode.window._activeTextEditorListeners) {
+			setTimeout(() => {
+				vscode.window._activeTextEditorListeners.forEach((listener) => {
+					try {
+						listener(mockEditor)
+					} catch (error) {
+						console.error("Error calling onDidChangeActiveTextEditor listener:", error)
+					}
+				})
+			}, 10) // Small delay to simulate async behavior
+		}
+
+		return mockEditor
 	},
 	createOutputChannel: (name) => {
 		console.log("Stubbed createOutputChannel:", name)
@@ -41,20 +253,60 @@ vscode.window = {
 		}
 	},
 	createTerminal: (...args) => {
-		console.log("Stubbed createTerminal:", ...args)
-		return {
-			sendText: console.log,
-			show: () => {},
-			dispose: () => {},
+		console.log("Enhanced createTerminal:", ...args)
+
+		// Extract options from arguments
+		let options = {}
+		if (args.length > 0) {
+			if (typeof args[0] === "string") {
+				// Called with (name, shellPath, shellArgs)
+				options = {
+					name: args[0],
+					shellPath: args[1],
+					shellArgs: args[2],
+				}
+			} else if (typeof args[0] === "object") {
+				// Called with options object
+				options = args[0]
+			}
+		}
+
+		// Use our enhanced terminal manager to create a terminal
+		const terminalInfo = globalTerminalManager.registry.createTerminal({
+			name: options.name || `Terminal ${Date.now()}`,
+			cwd: options.cwd || process.cwd(),
+			shellPath: options.shellPath,
+		})
+
+		// Store reference for tracking
+		vscode.window.terminals.push(terminalInfo.terminal)
+		if (!vscode.window.activeTerminal) {
+			vscode.window.activeTerminal = terminalInfo.terminal
 		}
+
+		console.log(`Enhanced terminal created: ${terminalInfo.id}`)
+		return terminalInfo.terminal
 	},
 	activeTextEditor: undefined,
 	visibleTextEditors: [],
 	tabGroups: {
-		all: [],
-		close: async () => {},
-		onDidChangeTabs: createStub("vscode.env.tabGroups.onDidChangeTabs"),
-		activeTabGroup: { tabs: [] },
+		all: [
+			{
+				tabs: [],
+				isActive: true,
+				viewColumn: 1,
+			},
+		],
+		activeTabGroup: {
+			tabs: [],
+			isActive: true,
+			viewColumn: 1,
+		},
+		close: async (tab) => {
+			console.log("Stubbed tabGroups.close:", tab)
+			return true
+		},
+		onDidChangeTabs: createStub("vscode.window.tabGroups.onDidChangeTabs"),
 	},
 	withProgress: async (_options, task) => {
 		console.log("Stubbed withProgress")
@@ -62,14 +314,56 @@ vscode.window = {
 	},
 	registerUriHandler: () => ({ dispose: () => {} }),
 	registerWebviewViewProvider: () => ({ dispose: () => {} }),
-	onDidChangeActiveTextEditor: () => ({ dispose: () => {} }),
+	onDidChangeActiveTextEditor: (listener) => {
+		console.log("Called vscode.window.onDidChangeActiveTextEditor")
+		// Store the listener so we can call it when showTextDocument is called
+		vscode.window._activeTextEditorListeners = vscode.window._activeTextEditorListeners || []
+		vscode.window._activeTextEditorListeners.push(listener)
+		return {
+			dispose: () => {
+				console.log("Disposed onDidChangeActiveTextEditor listener")
+				const index = vscode.window._activeTextEditorListeners.indexOf(listener)
+				if (index > -1) {
+					vscode.window._activeTextEditorListeners.splice(index, 1)
+				}
+			},
+		}
+	},
 	createTextEditorDecorationType: () => ({ dispose: () => {} }),
-	createWebviewPanel: (..._args) => {
-		throw new Error("WebviewPanel is not supported in standalone app.")
+	createWebviewPanel: (...args) => {
+		console.log("Stubbed createWebviewPanel:", ...args)
+		return {
+			webview: {},
+			reveal: () => {},
+			dispose: () => {},
+		}
+	},
+	onDidChangeTerminalState: (listener) => {
+		console.log("Called vscode.window.onDidChangeTerminalState")
+		return {
+			dispose: () => {
+				console.log("Disposed onDidChangeTerminalState listener")
+			},
+		}
+	},
+	onDidChangeTextEditorVisibleRanges: (listener) => {
+		console.log("Called vscode.window.onDidChangeTextEditorVisibleRanges")
+		return {
+			dispose: () => {
+				console.log("Disposed onDidChangeTextEditorVisibleRanges listener")
+			},
+		}
 	},
+	terminals: [],
+	activeTerminal: null,
 }
 
-vscode.env = {
+// Initialize env object if it doesn't exist, then extend it
+if (!vscode.env) {
+	vscode.env = {}
+}
+
+Object.assign(vscode.env, {
 	uriScheme: "vscode",
 	appName: "Visual Studio Code",
 	appRoot: "/tmp/vscode/appRoot",
@@ -79,17 +373,26 @@ vscode.env = {
 	sessionId: "stub-session-id",
 	shell: "/bin/bash",
 
+	// Add the stub functions that were missing
 	clipboard: createStub("vscode.env.clipboard"),
-	openExternal: createStub("vscode.env.openExternal"),
 	getQueryParameter: createStub("vscode.env.getQueryParameter"),
 	onDidChangeTelemetryEnabled: createStub("vscode.env.onDidChangeTelemetryEnabled"),
 	isTelemetryEnabled: createStub("vscode.env.isTelemetryEnabled"),
 	telemetryConfiguration: createStub("vscode.env.telemetryConfiguration"),
 	onDidChangeTelemetryConfiguration: createStub("vscode.env.onDidChangeTelemetryConfiguration"),
 	createTelemetryLogger: createStub("vscode.env.createTelemetryLogger"),
+})
+
+// Override the openExternal function with actual implementation
+vscode.env.openExternal = async (uri) => {
+	const url = typeof uri === "string" ? uri : (uri.toString?.() ?? "")
+	console.log("Opening browser:", url)
+	await open(url)
+	return true
 }
 
-vscode.Uri = {
+// Extend Uri object with improved implementations
+Object.assign(vscode.Uri, {
 	parse: (uriString) => {
 		const url = new URL(uriString)
 		return {
@@ -134,13 +437,405 @@ vscode.Uri = {
 		const joined = segments.map((s) => (typeof s === "string" ? s : s.path)).join("/")
 		return vscode.Uri.file("/" + joined.replace(/\/+/g, "/"))
 	},
+})
+
+// Extend workspace object with file system operations
+Object.assign(vscode.workspace, {
+	fs: {
+		readFile: async function (uri) {
+			console.log(`Called vscode.workspace.fs.readFile with uri:`, uri)
+			try {
+				// Extract file path from URI
+				let filePath = uri.path || uri.fsPath || uri
+				if (typeof filePath !== "string") {
+					filePath = uri.toString()
+				}
+
+				// Remove file:// prefix if present
+				if (filePath.startsWith("file://")) {
+					filePath = filePath.substring(7)
+				}
+
+				console.log(`Reading file: ${filePath}`)
+				const content = await fs.promises.readFile(filePath, "utf8")
+				console.log(
+					`File content read (${content.length} chars):`,
+					content.substring(0, 100) + (content.length > 100 ? "..." : ""),
+				)
+				return new Uint8Array(Buffer.from(content, "utf8"))
+			} catch (error) {
+				console.error(`Error reading file:`, error)
+				throw error
+			}
+		},
+
+		writeFile: async function (uri, content) {
+			console.log(`Called vscode.workspace.fs.writeFile with uri:`, uri)
+			try {
+				// Extract file path from URI
+				let filePath = uri.path || uri.fsPath || uri
+				if (typeof filePath !== "string") {
+					filePath = uri.toString()
+				}
+
+				// Remove file:// prefix if present
+				if (filePath.startsWith("file://")) {
+					filePath = filePath.substring(7)
+				}
+
+				console.log(`Writing file: ${filePath}`)
+
+				// Ensure directory exists
+				const dir = path.dirname(filePath)
+				await fs.promises.mkdir(dir, { recursive: true })
+
+				// Write the file
+				await fs.promises.writeFile(filePath, content)
+			} catch (error) {
+				console.error(`Error writing file:`, error)
+				throw error
+			}
+		},
+	},
+
+	// Add workspace folder configuration
+	rootPath: process.cwd(),
+	workspaceFolders: [
+		{
+			uri: vscode.Uri.file(process.cwd()),
+			name: path.basename(process.cwd()),
+			index: 0,
+		},
+	],
+	name: path.basename(process.cwd()),
+	workspaceFile: vscode.Uri.file(path.join(process.cwd(), ".vscode", "workspace.json")),
+
+	// Add other workspace methods as stubs
+	getConfiguration: () => ({
+		get: () => undefined,
+		update: () => Promise.resolve(),
+		has: () => false,
+	}),
+	getWorkspaceFolder: (uri) => {
+		console.log("Called vscode.workspace.getWorkspaceFolder with:", uri)
+		// Return the first workspace folder for any URI in standalone mode
+		if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
+			return vscode.workspace.workspaceFolders[0]
+		}
+		return undefined
+	},
+	createFileSystemWatcher: () => ({
+		onDidChange: () => ({ dispose: () => {} }),
+		onDidCreate: () => ({ dispose: () => {} }),
+		onDidDelete: () => ({ dispose: () => {} }),
+		dispose: () => {},
+	}),
+	onDidChangeConfiguration: () => ({ dispose: () => {} }),
+	onDidChangeWorkspaceFolders: () => ({ dispose: () => {} }),
+	onDidCreateFiles: createStub("vscode.workspace.onDidCreateFiles"),
+	onDidDeleteFiles: createStub("vscode.workspace.onDidDeleteFiles"),
+	onDidRenameFiles: createStub("vscode.workspace.onDidRenameFiles"),
+	onWillCreateFiles: createStub("vscode.workspace.onWillCreateFiles"),
+	onWillDeleteFiles: createStub("vscode.workspace.onWillDeleteFiles"),
+	onWillRenameFiles: createStub("vscode.workspace.onWillRenameFiles"),
+	textDocuments: {
+		find: (predicate) => {
+			console.log("Called vscode.workspace.textDocuments.find")
+			// Return a mock text document that behaves like VSCode expects
+			return {
+				uri: { fsPath: "/tmp/mock-document" },
+				fileName: "/tmp/mock-document",
+				isDirty: false,
+				save: async () => {
+					console.log("Called mock textDocument.save")
+					return true
+				},
+				getText: () => "",
+				lineCount: 0,
+			}
+		},
+		forEach: (callback) => {
+			console.log("Called vscode.workspace.textDocuments.forEach")
+			// No documents to iterate over in standalone mode
+		},
+		length: 0,
+		[Symbol.iterator]: function* () {
+			// Empty iterator for standalone mode
+		},
+	},
+
+	// Add the crucial applyEdit method
+	applyEdit: async (workspaceEdit) => {
+		console.log("Called vscode.workspace.applyEdit", workspaceEdit)
+
+		// For standalone mode, we'll simulate applying the edit by actually writing to files
+		try {
+			// WorkspaceEdit can contain multiple types of edits
+			if (workspaceEdit._edits) {
+				for (const edit of workspaceEdit._edits) {
+					if (edit._type === 1) {
+						// TextEdit
+						const uri = edit._uri
+						const edits = edit._edits
+
+						let filePath = uri.path || uri.fsPath
+						if (filePath.startsWith("file://")) {
+							filePath = filePath.substring(7)
+						}
+
+						console.log(`Applying text edits to: ${filePath}`)
+
+						// Read current content if file exists
+						let currentContent = ""
+						try {
+							currentContent = await fs.promises.readFile(filePath, "utf8")
+						} catch (e) {
+							// File doesn't exist, start with empty content
+							console.log(`File ${filePath} doesn't exist, starting with empty content`)
+						}
+
+						// Apply edits in reverse order (from end to beginning) to maintain positions
+						const sortedEdits = edits.sort((a, b) => {
+							const aStart = a.range.start.line * 1000000 + a.range.start.character
+							const bStart = b.range.start.line * 1000000 + b.range.start.character
+							return bStart - aStart
+						})
+
+						let lines = currentContent.split("\n")
+
+						for (const edit of sortedEdits) {
+							const startLine = edit.range.start.line
+							const startChar = edit.range.start.character
+							const endLine = edit.range.end.line
+							const endChar = edit.range.end.character
+							const newText = edit.newText
+
+							console.log(`Applying edit: ${startLine}:${startChar} - ${endLine}:${endChar} -> "${newText}"`)
+
+							// Handle the edit
+							if (startLine === endLine) {
+								// Single line edit
+								const line = lines[startLine] || ""
+								lines[startLine] = line.substring(0, startChar) + newText + line.substring(endChar)
+							} else {
+								// Multi-line edit
+								const firstLine = lines[startLine] || ""
+								const lastLine = lines[endLine] || ""
+								const newFirstLine = firstLine.substring(0, startChar) + newText + lastLine.substring(endChar)
+
+								// Replace the range with the new content
+								lines.splice(startLine, endLine - startLine + 1, newFirstLine)
+							}
+						}
+
+						const newContent = lines.join("\n")
+
+						// Ensure directory exists
+						const dir = path.dirname(filePath)
+						await fs.promises.mkdir(dir, { recursive: true })
+
+						// Write the updated content
+						await fs.promises.writeFile(filePath, newContent, "utf8")
+						console.log(`Successfully applied edits to: ${filePath}`)
+					}
+				}
+			}
+
+			return true
+		} catch (error) {
+			console.error("Error applying workspace edit:", error)
+			return false
+		}
+	},
+})
+
+// Fix CodeActionKind to have static properties instead of being a class
+vscode.CodeActionKind = {
+	Empty: "",
+	QuickFix: "quickfix",
+	Refactor: "refactor",
+	RefactorExtract: "refactor.extract",
+	RefactorInline: "refactor.inline",
+	RefactorRewrite: "refactor.rewrite",
+	Source: "source",
+	SourceOrganizeImports: "source.organizeImports",
+	SourceFixAll: "source.fixAll",
 }
 
-vscode.env.openExternal = async (uri) => {
-	const url = typeof uri === "string" ? uri : (uri.toString?.() ?? "")
-	console.log("Opening browser:", url)
-	await open(url)
-	return true
+// Add missing commands implementation
+if (!vscode.commands) {
+	vscode.commands = {}
+}
+
+Object.assign(vscode.commands, {
+	executeCommand: async (command, ...args) => {
+		console.log(`Called vscode.commands.executeCommand: ${command}`, args)
+
+		// Handle the vscode.diff command specifically
+		if (command === "vscode.diff") {
+			const [originalUri, modifiedUri, title, options] = args
+			console.log("Opening diff view:", { originalUri, modifiedUri, title })
+
+			// For standalone mode, just open the modified file directly
+			// since we can't show a proper diff view
+			const editor = await vscode.window.showTextDocument(modifiedUri, {
+				preserveFocus: options?.preserveFocus || false,
+				preview: false,
+			})
+
+			// Ensure the onDidChangeActiveTextEditor event fires with a slight delay
+			// This is crucial for DiffViewProvider.openDiffEditor() to work properly
+			setTimeout(() => {
+				if (vscode.window._activeTextEditorListeners) {
+					vscode.window._activeTextEditorListeners.forEach((listener) => {
+						try {
+							listener(editor)
+						} catch (error) {
+							console.error("Error calling onDidChangeActiveTextEditor listener in vscode.diff:", error)
+						}
+					})
+				}
+			}, 50) // Slightly longer delay to ensure proper event ordering
+
+			return editor
+		}
+
+		// For other commands, just return a resolved promise
+		return Promise.resolve()
+	},
+	registerCommand: (command, callback) => {
+		console.log(`Registered command: ${command}`)
+		return { dispose: () => {} }
+	},
+	getCommands: async () => {
+		return []
+	},
+})
+
+// Add missing TabInput classes
+vscode.TabInputText = class TabInputText {
+	constructor(uri) {
+		this.uri = uri
+	}
+}
+
+vscode.TabInputTextDiff = class TabInputTextDiff {
+	constructor(original, modified) {
+		this.original = original
+		this.modified = modified
+	}
+}
+
+// Add missing WorkspaceEdit and related classes
+vscode.WorkspaceEdit = class WorkspaceEdit {
+	constructor() {
+		this._edits = []
+	}
+
+	replace(uri, range, newText) {
+		console.log("WorkspaceEdit.replace:", uri, range, newText)
+		this._edits.push({
+			_type: 1, // TextEdit
+			_uri: uri,
+			_edits: [
+				{
+					range: range,
+					newText: newText,
+				},
+			],
+		})
+	}
+
+	insert(uri, position, newText) {
+		console.log("WorkspaceEdit.insert:", uri, position, newText)
+		this.replace(uri, new vscode.Range(position, position), newText)
+	}
+
+	delete(uri, range) {
+		console.log("WorkspaceEdit.delete:", uri, range)
+		this.replace(uri, range, "")
+	}
+}
+
+vscode.Range = class Range {
+	constructor(startLine, startCharacter, endLine, endCharacter) {
+		if (typeof startLine === "object") {
+			// Called with Position objects
+			this.start = startLine
+			this.end = startCharacter
+		} else {
+			// Called with line/character numbers
+			this.start = new vscode.Position(startLine, startCharacter)
+			this.end = new vscode.Position(endLine, endCharacter)
+		}
+	}
+}
+
+vscode.Position = class Position {
+	constructor(line, character) {
+		this.line = line
+		this.character = character
+	}
 }
 
+vscode.Selection = class Selection extends vscode.Range {
+	constructor(anchorLine, anchorCharacter, activeLine, activeCharacter) {
+		if (typeof anchorLine === "object") {
+			// Called with Position objects
+			super(anchorLine, anchorCharacter)
+			this.anchor = anchorLine
+			this.active = anchorCharacter
+		} else {
+			// Called with line/character numbers
+			super(anchorLine, anchorCharacter, activeLine, activeCharacter)
+			this.anchor = new vscode.Position(anchorLine, anchorCharacter)
+			this.active = new vscode.Position(activeLine, activeCharacter)
+		}
+	}
+}
+
+// Add TextEditorRevealType enum
+vscode.TextEditorRevealType = {
+	Default: 0,
+	InCenter: 1,
+	InCenterIfOutsideViewport: 2,
+	AtTop: 3,
+}
+
+// Add missing languages API
+if (!vscode.languages) {
+	vscode.languages = {}
+}
+
+Object.assign(vscode.languages, {
+	getDiagnostics: (uri) => {
+		console.log("Called vscode.languages.getDiagnostics")
+		// Return empty diagnostics for standalone mode
+		if (uri) {
+			return []
+		} else {
+			// Return all diagnostics as empty array
+			return []
+		}
+	},
+	registerCodeActionsProvider: () => ({ dispose: () => {} }),
+	createDiagnosticCollection: () => ({
+		set: () => {},
+		delete: () => {},
+		clear: () => {},
+		dispose: () => {},
+	}),
+})
+
 console.log("Finished loading stub impls...")
+
+// Export the terminal manager globally for Cline core to use
+global.standaloneTerminalManager = globalTerminalManager
+
+// Override the TerminalManager to use our standalone implementation
+if (typeof global !== "undefined") {
+	// Replace the TerminalManager class with our standalone implementation
+	global.StandaloneTerminalManagerClass = require("./enhanced-terminal").StandaloneTerminalManager
+}
+
+module.exports = vscode