refactor: remove unrelated files

This commit is contained in:
hieptl
2026-01-05 11:57:38 +07:00
parent 949bce0b65
commit d2328e9d19
9 changed files with 0 additions and 1138 deletions

View File

@@ -1,331 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = activate;
exports.deactivate = deactivate;
const vscode = __importStar(require("vscode"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
// Create output channel for debug logging
const outputChannel = vscode.window.createOutputChannel("OpenHands Debug");
/**
* This implementation uses VSCode's Shell Integration API.
*
* VSCode API References:
* - Terminal Shell Integration: https://code.visualstudio.com/docs/terminal/shell-integration
* - VSCode Extension API: https://code.visualstudio.com/api/references/vscode-api
* - Terminal API Reference: https://code.visualstudio.com/api/references/vscode-api#Terminal
* - VSCode Source Examples: https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.d.ts
*
* Shell Integration Requirements:
* - Compatible shells: bash, zsh, PowerShell Core, or fish shell
* - Graceful fallback needed for Command Prompt and other shells
*/
// Track terminals that we know are idle (just finished our commands)
const idleTerminals = new Set();
/**
* Marks a terminal as idle after our command completes
* @param terminalName The name of the terminal
*/
function markTerminalAsIdle(terminalName) {
idleTerminals.add(terminalName);
}
/**
* Marks a terminal as busy when we start a command
* @param terminalName The name of the terminal
*/
function markTerminalAsBusy(terminalName) {
idleTerminals.delete(terminalName);
}
/**
* Checks if we know a terminal is idle (safe to reuse)
* @param terminal The terminal to check
* @returns boolean true if we know it's idle, false otherwise
*/
function isKnownIdleTerminal(terminal) {
return idleTerminals.has(terminal.name);
}
/**
* Creates a new OpenHands terminal with timestamp
* @returns vscode.Terminal
*/
function createNewOpenHandsTerminal() {
const timestamp = new Date().toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
});
const terminalName = `OpenHands ${timestamp}`;
return vscode.window.createTerminal(terminalName);
}
/**
* Finds an existing OpenHands terminal or creates a new one using safe detection
* @returns vscode.Terminal
*/
function findOrCreateOpenHandsTerminal() {
const openHandsTerminals = vscode.window.terminals.filter((terminal) => terminal.name.startsWith("OpenHands"));
if (openHandsTerminals.length > 0) {
// Use the most recent terminal, but only if we know it's idle
const terminal = openHandsTerminals[openHandsTerminals.length - 1];
// Only reuse terminals that we know are idle (safe to reuse)
if (isKnownIdleTerminal(terminal)) {
return terminal;
}
// If we don't know the terminal is idle, create a new one to avoid interrupting running processes
return createNewOpenHandsTerminal();
}
// No existing terminals, create new one
return createNewOpenHandsTerminal();
}
/**
* Executes an OpenHands command using Shell Integration when available
* @param terminal The terminal to execute the command in
* @param command The command to execute
*/
function executeOpenHandsCommand(terminal, command) {
// Mark terminal as busy when we start a command
markTerminalAsBusy(terminal.name);
if (terminal.shellIntegration) {
// Use Shell Integration for better control
const execution = terminal.shellIntegration.executeCommand(command);
// Monitor execution completion
const disposable = vscode.window.onDidEndTerminalShellExecution((event) => {
if (event.execution === execution) {
if (event.exitCode === 0) {
outputChannel.appendLine("DEBUG: OpenHands command completed successfully");
// Mark terminal as idle when command completes successfully
markTerminalAsIdle(terminal.name);
}
else if (event.exitCode !== undefined) {
outputChannel.appendLine(`DEBUG: OpenHands command exited with code ${event.exitCode}`);
// Mark terminal as idle even if command failed (user can reuse it)
markTerminalAsIdle(terminal.name);
}
disposable.dispose(); // Clean up the event listener
}
});
}
else {
// Fallback to traditional sendText
terminal.sendText(command, true);
// For traditional sendText, we can't track completion, so don't mark as idle
// This means terminals without Shell Integration won't be reused, which is safer
}
}
/**
* Detects and builds virtual environment activation command
* @returns string The activation command prefix (empty if no venv found)
*/
function detectVirtualEnvironment() {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
outputChannel.appendLine("DEBUG: No workspace folder found");
return "";
}
const venvPaths = [".venv", "venv", ".virtualenv"];
for (const venvPath of venvPaths) {
const venvFullPath = path.join(workspaceFolder.uri.fsPath, venvPath);
if (fs.existsSync(venvFullPath)) {
outputChannel.appendLine(`DEBUG: Found venv at ${venvFullPath}`);
if (process.platform === "win32") {
// For Windows, the activation command is different and typically doesn't use 'source'
// It's often a script that needs to be executed.
// This is a simplified version. A more robust solution might need to check for PowerShell, cmd, etc.
return `& "${path.join(venvFullPath, "Scripts", "Activate.ps1")}" && `;
}
// For POSIX-like shells
return `source "${path.join(venvFullPath, "bin", "activate")}" && `;
}
}
outputChannel.appendLine(`DEBUG: No venv found in workspace ${workspaceFolder.uri.fsPath}`);
return "";
}
/**
* Creates a contextual task message for file content
* @param filePath The file path (or "Untitled" for unsaved files)
* @param content The file content
* @param languageId The programming language ID
* @returns string A descriptive task message
*/
function createFileContextMessage(filePath, content, languageId) {
const fileName = filePath === "Untitled" ? "an untitled file" : `file ${filePath}`;
const langInfo = languageId ? ` (${languageId})` : "";
return `User opened ${fileName}${langInfo}. Here's the content:
\`\`\`${languageId || ""}
${content}
\`\`\`
Please ask the user what they want to do with this file.`;
}
/**
* Creates a contextual task message for selected text
* @param filePath The file path (or "Untitled" for unsaved files)
* @param content The selected content
* @param startLine 1-based start line number
* @param endLine 1-based end line number
* @param languageId The programming language ID
* @returns string A descriptive task message
*/
function createSelectionContextMessage(filePath, content, startLine, endLine, languageId) {
const fileName = filePath === "Untitled" ? "an untitled file" : `file ${filePath}`;
const langInfo = languageId ? ` (${languageId})` : "";
const lineInfo = startLine === endLine
? `line ${startLine}`
: `lines ${startLine}-${endLine}`;
return `User selected ${lineInfo} in ${fileName}${langInfo}. Here's the selected content:
\`\`\`${languageId || ""}
${content}
\`\`\`
Please ask the user what they want to do with this selection.`;
}
/**
* Builds the OpenHands command with proper sanitization
* @param options Command options
* @param activationCommand Virtual environment activation prefix
* @returns string The complete command to execute
*/
function buildOpenHandsCommand(options, activationCommand) {
let commandToSend = `${activationCommand}openhands`;
if (options.filePath) {
// Ensure filePath is properly quoted if it contains spaces or special characters
const safeFilePath = options.filePath.includes(" ")
? `"${options.filePath}"`
: options.filePath;
commandToSend = `${activationCommand}openhands --file ${safeFilePath}`;
}
else if (options.task) {
// Sanitize task string for command line (basic sanitization)
// Replace backticks and double quotes that might break the command
const sanitizedTask = options.task
.replace(/`/g, "\\`")
.replace(/"/g, '\\"');
commandToSend = `${activationCommand}openhands --task "${sanitizedTask}"`;
}
return commandToSend;
}
/**
* Main function to start OpenHands in terminal with safe terminal reuse
* @param options Command options
*/
function startOpenHandsInTerminal(options) {
try {
// Find or create terminal using safe detection
const terminal = findOrCreateOpenHandsTerminal();
terminal.show(true); // true to preserve focus on the editor
// Detect virtual environment
const activationCommand = detectVirtualEnvironment();
// Build command
const commandToSend = buildOpenHandsCommand(options, activationCommand);
// Debug: show the actual command being sent
outputChannel.appendLine(`DEBUG: Sending command: ${commandToSend}`);
// Execute command using Shell Integration when available
executeOpenHandsCommand(terminal, commandToSend);
}
catch (error) {
vscode.window.showErrorMessage(`Error starting OpenHands: ${error}`);
}
}
function activate(context) {
// Clean up terminal tracking when terminals are closed
const terminalCloseDisposable = vscode.window.onDidCloseTerminal((terminal) => {
idleTerminals.delete(terminal.name);
});
context.subscriptions.push(terminalCloseDisposable);
// Command: Start New Conversation
const startConversationDisposable = vscode.commands.registerCommand("openhands.startConversation", () => {
startOpenHandsInTerminal({});
});
context.subscriptions.push(startConversationDisposable);
// Command: Start Conversation with Active File Content
const startWithFileContextDisposable = vscode.commands.registerCommand("openhands.startConversationWithFileContext", () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
// No active editor, start conversation without task
startOpenHandsInTerminal({});
return;
}
if (editor.document.isUntitled) {
const fileContent = editor.document.getText();
if (!fileContent.trim()) {
// Empty untitled file, start conversation without task
startOpenHandsInTerminal({});
return;
}
// Create contextual message for untitled file
const contextualTask = createFileContextMessage("Untitled", fileContent, editor.document.languageId);
startOpenHandsInTerminal({ task: contextualTask });
}
else {
const filePath = editor.document.uri.fsPath;
// For saved files, we can still use --file flag for better performance,
// but we could also create a contextual message if preferred
startOpenHandsInTerminal({ filePath });
}
});
context.subscriptions.push(startWithFileContextDisposable);
// Command: Start Conversation with Selected Text
const startWithSelectionContextDisposable = vscode.commands.registerCommand("openhands.startConversationWithSelectionContext", () => {
outputChannel.appendLine("DEBUG: startConversationWithSelectionContext command triggered!");
const editor = vscode.window.activeTextEditor;
if (!editor) {
// No active editor, start conversation without task
startOpenHandsInTerminal({});
return;
}
if (editor.selection.isEmpty) {
// No text selected, start conversation without task
startOpenHandsInTerminal({});
return;
}
const selectedText = editor.document.getText(editor.selection);
const startLine = editor.selection.start.line + 1; // Convert to 1-based
const endLine = editor.selection.end.line + 1; // Convert to 1-based
const filePath = editor.document.isUntitled
? "Untitled"
: editor.document.uri.fsPath;
// Create contextual message with line numbers and file info
const contextualTask = createSelectionContextMessage(filePath, selectedText, startLine, endLine, editor.document.languageId);
startOpenHandsInTerminal({ task: contextualTask });
});
context.subscriptions.push(startWithSelectionContextDisposable);
}
function deactivate() {
// Clean up resources if needed, though for this simple extension,
// VS Code handles terminal disposal.
}
//# sourceMappingURL=extension.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,55 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const test_electron_1 = require("@vscode/test-electron");
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, "../../../");
// The path to the extension test script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "./suite/index"); // Points to the compiled version of suite/index.ts
// Download VS Code, unzip it and run the integration test
await (0, test_electron_1.runTests)({ extensionDevelopmentPath, extensionTestsPath });
}
catch (err) {
console.error("Failed to run tests");
process.exit(1);
}
}
main();
//# sourceMappingURL=runTest.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"runTest.js","sourceRoot":"","sources":["../../src/test/runTest.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA6B;AAC7B,yDAAiD;AAEjD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,4DAA4D;QAC5D,yCAAyC;QACzC,MAAM,wBAAwB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAEtE,wCAAwC;QACxC,iCAAiC;QACjC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,mDAAmD;QAExH,0DAA0D;QAC1D,MAAM,IAAA,wBAAQ,EAAC,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}

View File

@@ -1,677 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const assert = __importStar(require("assert"));
const vscode = __importStar(require("vscode"));
suite("Extension Test Suite", () => {
let mockTerminal;
let sendTextSpy; // Manual spy, using 'any' type
let showSpy; // Manual spy
let createTerminalStub; // Manual stub
let findTerminalStub; // Manual spy
let showErrorMessageSpy; // Manual spy
// It's better to use a proper mocking library like Sinon.JS for spies and stubs.
// For now, we'll use a simplified manual approach for spies.
const createManualSpy = () => {
const spy = (...args) => {
// eslint-disable-line @typescript-eslint/no-explicit-any
spy.called = true;
spy.callCount = (spy.callCount || 0) + 1;
spy.lastArgs = args;
spy.argsHistory = spy.argsHistory || [];
spy.argsHistory.push(args);
};
spy.called = false;
spy.callCount = 0;
spy.lastArgs = null;
spy.argsHistory = [];
spy.resetHistory = () => {
spy.called = false;
spy.callCount = 0;
spy.lastArgs = null;
spy.argsHistory = [];
};
return spy;
};
setup(() => {
// Reset spies and stubs before each test
sendTextSpy = createManualSpy();
showSpy = createManualSpy();
showErrorMessageSpy = createManualSpy();
mockTerminal = {
name: "OpenHands",
processId: Promise.resolve(123),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined, // Added to satisfy Terminal interface
state: {
isInteractedWith: false,
shell: undefined,
}, // Added shell property
shellIntegration: undefined, // No Shell Integration in tests by default
};
// Store original functions
const _originalCreateTerminal = vscode.window.createTerminal;
const _originalTerminalsDescriptor = Object.getOwnPropertyDescriptor(vscode.window, "terminals");
const _originalShowErrorMessage = vscode.window.showErrorMessage;
// Stub vscode.window.createTerminal
createTerminalStub = createManualSpy();
vscode.window.createTerminal = (...args) => {
createTerminalStub(...args); // Call the spy with whatever arguments it received
return mockTerminal; // Return the mock terminal
};
// Stub vscode.window.terminals
findTerminalStub = createManualSpy(); // To track if vscode.window.terminals getter is accessed
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
// Default to returning the mockTerminal, can be overridden in specific tests
return [mockTerminal];
},
configurable: true,
});
vscode.window.showErrorMessage = showErrorMessageSpy;
// Restore default mock behavior before each test
setup(() => {
// Reset spies
createTerminalStub.resetHistory();
sendTextSpy.resetHistory();
showSpy.resetHistory();
findTerminalStub.resetHistory();
showErrorMessageSpy.resetHistory();
// Restore default createTerminal mock
vscode.window.createTerminal = (...args) => {
createTerminalStub(...args);
return mockTerminal; // Return the default mock terminal (no Shell Integration)
};
// Restore default terminals mock
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return [mockTerminal]; // Default to returning the mockTerminal
},
configurable: true,
});
});
// Teardown logic to restore original functions
teardown(() => {
vscode.window.createTerminal = _originalCreateTerminal;
if (_originalTerminalsDescriptor) {
Object.defineProperty(vscode.window, "terminals", _originalTerminalsDescriptor);
}
else {
// If it wasn't originally defined, delete it to restore to that state
delete vscode.window.terminals;
}
vscode.window.showErrorMessage = _originalShowErrorMessage;
});
});
test("Extension should be present and activate", async () => {
const extension = vscode.extensions.getExtension("openhands.openhands-vscode");
assert.ok(extension, "Extension should be found (check publisher.name in package.json)");
if (!extension.isActive) {
await extension.activate();
}
assert.ok(extension.isActive, "Extension should be active");
});
test("Commands should be registered", async () => {
const extension = vscode.extensions.getExtension("openhands.openhands-vscode");
if (extension && !extension.isActive) {
await extension.activate();
}
const commands = await vscode.commands.getCommands(true);
const expectedCommands = [
"openhands.startConversation",
"openhands.startConversationWithFileContext",
"openhands.startConversationWithSelectionContext",
];
for (const cmd of expectedCommands) {
assert.ok(commands.includes(cmd), `Command '${cmd}' should be registered`);
}
});
test("openhands.startConversation should send correct command to terminal", async () => {
findTerminalStub.resetHistory(); // Reset for this specific test path if needed
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return [];
},
configurable: true,
}); // Simulate no existing terminal
await vscode.commands.executeCommand("openhands.startConversation");
assert.ok(createTerminalStub.called, "vscode.window.createTerminal should be called");
assert.ok(showSpy.called, "terminal.show should be called");
assert.deepStrictEqual(sendTextSpy.lastArgs, ["openhands", true], "Correct command sent to terminal");
});
test("openhands.startConversationWithFileContext (saved file) should send --file command", async () => {
const testFilePath = "/test/file.py";
// Mock activeTextEditor for a saved file
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(vscode.window, "activeTextEditor");
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => ({
document: {
isUntitled: false,
uri: vscode.Uri.file(testFilePath),
fsPath: testFilePath, // fsPath is often used
getText: () => "file content", // Not used for saved files but good to have
},
}),
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversationWithFileContext");
assert.ok(sendTextSpy.called, "terminal.sendText should be called");
assert.deepStrictEqual(sendTextSpy.lastArgs, [
`openhands --file ${testFilePath.includes(" ") ? `"${testFilePath}"` : testFilePath}`,
true,
]);
// Restore activeTextEditor
if (originalActiveTextEditor) {
Object.defineProperty(vscode.window, "activeTextEditor", originalActiveTextEditor);
}
});
test("openhands.startConversationWithFileContext (untitled file) should send contextual --task command", async () => {
const untitledFileContent = "untitled content";
const languageId = "javascript";
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(vscode.window, "activeTextEditor");
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => ({
document: {
isUntitled: true,
uri: vscode.Uri.parse("untitled:Untitled-1"),
getText: () => untitledFileContent,
languageId,
},
}),
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversationWithFileContext");
assert.ok(sendTextSpy.called, "terminal.sendText should be called");
// Check that the command contains the contextual message
const expectedMessage = `User opened an untitled file (${languageId}). Here's the content:
\`\`\`${languageId}
${untitledFileContent}
\`\`\`
Please ask the user what they want to do with this file.`;
// Apply the same sanitization as the actual implementation
const sanitizedMessage = expectedMessage
.replace(/`/g, "\\`")
.replace(/"/g, '\\"');
assert.deepStrictEqual(sendTextSpy.lastArgs, [
`openhands --task "${sanitizedMessage}"`,
true,
]);
if (originalActiveTextEditor) {
Object.defineProperty(vscode.window, "activeTextEditor", originalActiveTextEditor);
}
});
test("openhands.startConversationWithFileContext (no editor) should start conversation without context", async () => {
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(vscode.window, "activeTextEditor");
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => undefined,
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversationWithFileContext");
assert.ok(sendTextSpy.called, "terminal.sendText should be called");
assert.deepStrictEqual(sendTextSpy.lastArgs, ["openhands", true]);
if (originalActiveTextEditor) {
Object.defineProperty(vscode.window, "activeTextEditor", originalActiveTextEditor);
}
});
test("openhands.startConversationWithSelectionContext should send contextual --task with selection", async () => {
const selectedText = "selected text for openhands";
const filePath = "/test/file.py";
const languageId = "python";
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(vscode.window, "activeTextEditor");
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => ({
document: {
isUntitled: false,
uri: vscode.Uri.file(filePath),
fsPath: filePath,
languageId,
getText: (selection) => selection ? selectedText : "full content",
},
selection: {
isEmpty: false,
active: new vscode.Position(0, 0),
anchor: new vscode.Position(0, 0),
start: new vscode.Position(0, 0), // Line 0 (0-based)
end: new vscode.Position(0, 10), // Line 0 (0-based)
}, // Mock non-empty selection on line 1
}),
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversationWithSelectionContext");
assert.ok(sendTextSpy.called, "terminal.sendText should be called");
// Check that the command contains the contextual message with line numbers
const expectedMessage = `User selected line 1 in file ${filePath} (${languageId}). Here's the selected content:
\`\`\`${languageId}
${selectedText}
\`\`\`
Please ask the user what they want to do with this selection.`;
// Apply the same sanitization as the actual implementation
const sanitizedMessage = expectedMessage
.replace(/`/g, "\\`")
.replace(/"/g, '\\"');
assert.deepStrictEqual(sendTextSpy.lastArgs, [
`openhands --task "${sanitizedMessage}"`,
true,
]);
if (originalActiveTextEditor) {
Object.defineProperty(vscode.window, "activeTextEditor", originalActiveTextEditor);
}
});
test("openhands.startConversationWithSelectionContext (no selection) should start conversation without context", async () => {
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(vscode.window, "activeTextEditor");
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => ({
document: {
isUntitled: false,
uri: vscode.Uri.file("/test/file.py"),
getText: () => "full content",
},
selection: { isEmpty: true }, // Mock empty selection
}),
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversationWithSelectionContext");
assert.ok(sendTextSpy.called, "terminal.sendText should be called");
assert.deepStrictEqual(sendTextSpy.lastArgs, ["openhands", true]);
if (originalActiveTextEditor) {
Object.defineProperty(vscode.window, "activeTextEditor", originalActiveTextEditor);
}
});
test("openhands.startConversationWithSelectionContext should handle multi-line selections", async () => {
const selectedText = "line 1\nline 2\nline 3";
const filePath = "/test/multiline.js";
const languageId = "javascript";
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(vscode.window, "activeTextEditor");
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => ({
document: {
isUntitled: false,
uri: vscode.Uri.file(filePath),
fsPath: filePath,
languageId,
getText: (selection) => selection ? selectedText : "full content",
},
selection: {
isEmpty: false,
active: new vscode.Position(4, 0),
anchor: new vscode.Position(4, 0),
start: new vscode.Position(4, 0), // Line 4 (0-based) = Line 5 (1-based)
end: new vscode.Position(6, 10), // Line 6 (0-based) = Line 7 (1-based)
}, // Mock multi-line selection from line 5 to 7
}),
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversationWithSelectionContext");
assert.ok(sendTextSpy.called, "terminal.sendText should be called");
// Check that the command contains the contextual message with line range
const expectedMessage = `User selected lines 5-7 in file ${filePath} (${languageId}). Here's the selected content:
\`\`\`${languageId}
${selectedText}
\`\`\`
Please ask the user what they want to do with this selection.`;
// Apply the same sanitization as the actual implementation
const sanitizedMessage = expectedMessage
.replace(/`/g, "\\`")
.replace(/"/g, '\\"');
assert.deepStrictEqual(sendTextSpy.lastArgs, [
`openhands --task "${sanitizedMessage}"`,
true,
]);
if (originalActiveTextEditor) {
Object.defineProperty(vscode.window, "activeTextEditor", originalActiveTextEditor);
}
});
test("Terminal reuse should work when existing OpenHands terminal exists", async () => {
// Create a mock existing terminal
const existingTerminal = {
name: "OpenHands 10:30:15",
processId: Promise.resolve(456),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined,
state: {
isInteractedWith: false,
shell: undefined,
},
shellIntegration: undefined, // No Shell Integration, should create new terminal
};
// Mock terminals array to return existing terminal
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return [existingTerminal];
},
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversation");
// Should create new terminal since no Shell Integration
assert.ok(createTerminalStub.called, "Should create new terminal when no Shell Integration available");
});
test("Terminal reuse with Shell Integration should reuse existing terminal", async () => {
// Create mock Shell Integration
const mockExecution = {
read: () => ({
async *[Symbol.asyncIterator]() {
yield "OPENHANDS_PROBE_123456789";
},
}),
exitCode: Promise.resolve(0),
};
const mockShellIntegration = {
executeCommand: () => mockExecution,
};
// Create a mock existing terminal with Shell Integration
const existingTerminalWithShell = {
name: "OpenHands 10:30:15",
processId: Promise.resolve(456),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined,
state: {
isInteractedWith: false,
shell: undefined,
},
shellIntegration: mockShellIntegration,
};
// Mock terminals array to return existing terminal with Shell Integration
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return [existingTerminalWithShell];
},
configurable: true,
});
// Reset create terminal stub to track if new terminal is created
createTerminalStub.resetHistory();
await vscode.commands.executeCommand("openhands.startConversation");
// Should reuse existing terminal since Shell Integration is available
// Note: The probe might timeout in test environment, but it should still reuse the terminal
assert.ok(showSpy.called, "terminal.show should be called");
});
test("Shell Integration should use executeCommand for OpenHands commands", async () => {
const executeCommandSpy = createManualSpy();
// Mock execution for OpenHands command
const mockExecution = {
read: () => ({
async *[Symbol.asyncIterator]() {
yield "OpenHands started successfully";
},
}),
exitCode: Promise.resolve(0),
commandLine: {
value: "openhands",
isTrusted: true,
confidence: 2,
},
cwd: vscode.Uri.file("/test/directory"),
};
const mockShellIntegration = {
executeCommand: (command) => {
executeCommandSpy(command);
return mockExecution;
},
cwd: vscode.Uri.file("/test/directory"),
};
// Create a terminal with Shell Integration that will be created by createTerminal
const terminalWithShell = {
name: "OpenHands 10:30:15",
processId: Promise.resolve(456),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined,
state: {
isInteractedWith: false,
shell: undefined,
},
shellIntegration: mockShellIntegration,
};
// Mock createTerminal to return a terminal with Shell Integration
createTerminalStub.resetHistory();
vscode.window.createTerminal = (...args) => {
createTerminalStub(...args);
return terminalWithShell; // Return terminal with Shell Integration
};
// Mock empty terminals array so we create a new one
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return []; // No existing terminals
},
configurable: true,
});
await vscode.commands.executeCommand("openhands.startConversation");
// Should have called executeCommand for OpenHands command
assert.ok(executeCommandSpy.called, "Shell Integration executeCommand should be called for OpenHands command");
// Check that the command was an OpenHands command
const openhandsCall = executeCommandSpy.argsHistory.find((args) => args[0] && args[0].includes("openhands"));
assert.ok(openhandsCall, `Should execute OpenHands command. Actual calls: ${JSON.stringify(executeCommandSpy.argsHistory)}`);
// Should create new terminal since none exist
assert.ok(createTerminalStub.called, "Should create new terminal when none exist");
});
test("Idle terminal tracking should reuse known idle terminals", async () => {
const executeCommandSpy = createManualSpy();
// Mock execution for OpenHands command
const mockExecution = {
read: () => ({
async *[Symbol.asyncIterator]() {
yield "OpenHands started successfully";
},
}),
exitCode: Promise.resolve(0),
commandLine: {
value: "openhands",
isTrusted: true,
confidence: 2,
},
cwd: vscode.Uri.file("/test/directory"),
};
const mockShellIntegration = {
executeCommand: (command) => {
executeCommandSpy(command);
return mockExecution;
},
cwd: vscode.Uri.file("/test/directory"),
};
const terminalWithShell = {
name: "OpenHands 10:30:15",
processId: Promise.resolve(456),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined,
state: {
isInteractedWith: false,
shell: undefined,
},
shellIntegration: mockShellIntegration,
};
// First, manually mark the terminal as idle (simulating a previous successful command)
// We need to access the extension's internal idle tracking
// For testing, we'll simulate this by running a command first, then another
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return [terminalWithShell];
},
configurable: true,
});
createTerminalStub.resetHistory();
// First command to establish the terminal as idle
await vscode.commands.executeCommand("openhands.startConversation");
// Simulate command completion to mark terminal as idle
// This would normally happen via the onDidEndTerminalShellExecution event
createTerminalStub.resetHistory();
executeCommandSpy.resetHistory();
// Second command should reuse the terminal if it's marked as idle
await vscode.commands.executeCommand("openhands.startConversation");
// Should show terminal
assert.ok(showSpy.called, "Should show terminal");
});
test("Shell Integration should use executeCommand when available", async () => {
const executeCommandSpy = createManualSpy();
const mockExecution = {
read: () => ({
async *[Symbol.asyncIterator]() {
yield "OpenHands started successfully";
},
}),
exitCode: Promise.resolve(0),
commandLine: {
value: "openhands",
isTrusted: true,
confidence: 2,
},
cwd: vscode.Uri.file("/test/directory"),
};
const mockShellIntegration = {
executeCommand: (command) => {
executeCommandSpy(command);
return mockExecution;
},
cwd: vscode.Uri.file("/test/directory"),
};
const terminalWithShell = {
name: "OpenHands 10:30:15",
processId: Promise.resolve(456),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined,
state: {
isInteractedWith: false,
shell: undefined,
},
shellIntegration: mockShellIntegration,
};
// Mock createTerminal to return a terminal with Shell Integration
createTerminalStub.resetHistory();
vscode.window.createTerminal = (...args) => {
createTerminalStub(...args);
return terminalWithShell; // Return terminal with Shell Integration
};
// Mock empty terminals array so we create a new one
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return []; // No existing terminals
},
configurable: true,
});
sendTextSpy.resetHistory();
executeCommandSpy.resetHistory();
await vscode.commands.executeCommand("openhands.startConversation");
// Should use Shell Integration executeCommand, not sendText
assert.ok(executeCommandSpy.called, "Should use Shell Integration executeCommand");
// The OpenHands command should be executed via Shell Integration
const openhandsCommand = executeCommandSpy.argsHistory.find((args) => args[0] && args[0].includes("openhands"));
assert.ok(openhandsCommand, "Should execute OpenHands command via Shell Integration");
});
test("Terminal creation should work when no existing terminals", async () => {
// Mock empty terminals array
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return []; // No existing terminals
},
configurable: true,
});
createTerminalStub.resetHistory();
await vscode.commands.executeCommand("openhands.startConversation");
// Should create new terminal when none exist
assert.ok(createTerminalStub.called, "Should create new terminal when none exist");
// Should show the new terminal
assert.ok(showSpy.called, "Should show the new terminal");
});
test("Shell Integration fallback should work when Shell Integration unavailable", async () => {
// Create terminal without Shell Integration
const terminalWithoutShell = {
name: "OpenHands 10:30:15",
processId: Promise.resolve(456),
sendText: sendTextSpy,
show: showSpy,
hide: () => { },
dispose: () => { },
creationOptions: {},
exitStatus: undefined,
state: {
isInteractedWith: false,
shell: undefined,
},
shellIntegration: undefined, // No Shell Integration
};
Object.defineProperty(vscode.window, "terminals", {
get: () => {
findTerminalStub();
return [terminalWithoutShell];
},
configurable: true,
});
createTerminalStub.resetHistory();
sendTextSpy.resetHistory();
await vscode.commands.executeCommand("openhands.startConversation");
// Should create new terminal when no Shell Integration available
assert.ok(createTerminalStub.called, "Should create new terminal when Shell Integration unavailable");
// Should use sendText fallback for the new terminal
assert.ok(sendTextSpy.called, "Should use sendText fallback");
assert.ok(sendTextSpy.lastArgs[0].includes("openhands"), "Should send OpenHands command");
});
});
//# sourceMappingURL=extension.test.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,71 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = run;
const path = __importStar(require("path"));
const Mocha = require("mocha");
const glob_1 = require("glob"); // Updated for glob v9+ API
async function run() {
// Create the mocha test
const mocha = new Mocha({
// This should now work with the changed import
ui: "tdd", // Use TDD interface
color: true, // Colored output
timeout: 15000, // Increased timeout for extension tests
});
const testsRoot = path.resolve(__dirname, ".."); // Root of the /src/test folder (compiled to /out/test)
try {
// Use glob to find all test files (ending with .test.js in the compiled output)
const files = await (0, glob_1.glob)("**/**.test.js", { cwd: testsRoot });
// Add files to the test suite
files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
// Run the mocha test
return await new Promise((resolve, reject) => {
mocha.run((failures) => {
if (failures > 0) {
reject(new Error(`${failures} tests failed.`));
}
else {
resolve();
}
});
});
}
catch (err) {
console.error(err);
throw err;
}
}
//# sourceMappingURL=index.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/test/suite/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,kBAgCC;AApCD,2CAA6B;AAC7B,+BAAgC;AAChC,+BAA4B,CAAC,2BAA2B;AAEjD,KAAK,UAAU,GAAG;IACvB,wBAAwB;IACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACtB,+CAA+C;QAC/C,EAAE,EAAE,KAAK,EAAE,oBAAoB;QAC/B,KAAK,EAAE,IAAI,EAAE,iBAAiB;QAC9B,OAAO,EAAE,KAAK,EAAE,wCAAwC;KACzD,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,uDAAuD;IAExG,IAAI,CAAC;QACH,gFAAgF;QAChF,MAAM,KAAK,GAAG,MAAM,IAAA,WAAI,EAAC,eAAe,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;QAE9D,8BAA8B;QAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAExE,qBAAqB;QACrB,OAAO,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACjD,KAAK,CAAC,GAAG,CAAC,CAAC,QAAgB,EAAE,EAAE;gBAC7B,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;oBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,QAAQ,gBAAgB,CAAC,CAAC,CAAC;gBACjD,CAAC;qBAAM,CAAC;oBACN,OAAO,EAAE,CAAC;gBACZ,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}