feat(vscode-ide-companion): improve CLI path detection and error handling

- Move determineNodePathForCli function to dedicated cliPathDetector.ts file
- Enhance error handling with specific guidance for permission issues
- Add detailed error messages for different failure scenarios
- Improve logging for debugging CLI path detection issues

This change improves the reliability of CLI path detection by providing better error messages and handling edge cases more gracefully.
This commit is contained in:
yiliang114
2025-12-03 00:27:20 +08:00
parent 2e449f4d45
commit c6299bf135
14 changed files with 788 additions and 198 deletions

View File

@@ -20,7 +20,7 @@ import type {
} from './connectionTypes.js';
import { AcpMessageHandler } from './acpMessageHandler.js';
import { AcpSessionManager } from './acpSessionManager.js';
import { statSync } from 'fs';
import { determineNodePathForCli } from '../cli/cliPathDetector.js';
/**
* ACP Connection Handler for VSCode Extension
@@ -66,74 +66,6 @@ export class AcpConnection {
this.sessionManager = new AcpSessionManager();
}
/**
* Determine the correct Node.js executable path for a given CLI installation
* Handles various Node.js version managers (nvm, n, manual installations)
*
* @param cliPath - Path to the CLI executable
* @returns Path to the Node.js executable, or null if not found
*/
private determineNodePathForCli(cliPath: string): string | null {
// Common patterns for Node.js installations
const nodePathPatterns = [
// NVM pattern: /Users/user/.nvm/versions/node/vXX.XX.X/bin/qwen -> /Users/user/.nvm/versions/node/vXX.XX.X/bin/node
cliPath.replace(/\/bin\/qwen$/, '/bin/node'),
// N pattern: /Users/user/n/bin/qwen -> /Users/user/n/bin/node
cliPath.replace(/\/bin\/qwen$/, '/bin/node'),
// Manual installation pattern: /usr/local/bin/qwen -> /usr/local/bin/node
cliPath.replace(/\/qwen$/, '/node'),
// Alternative pattern: /opt/nodejs/bin/qwen -> /opt/nodejs/bin/node
cliPath.replace(/\/bin\/qwen$/, '/bin/node'),
];
// Check each pattern
for (const nodePath of nodePathPatterns) {
try {
if (statSync(nodePath).isFile()) {
// Verify it's executable
const stats = statSync(nodePath);
if (stats.mode & 0o111) {
// Check if executable
console.log(
`[ACP] Found Node.js executable for CLI at: ${nodePath}`,
);
return nodePath;
}
}
} catch (_error) {
// File doesn't exist or other error, continue to next pattern
continue;
}
}
// Try to find node in the same directory as the CLI
const cliDir = cliPath.substring(0, cliPath.lastIndexOf('/'));
const potentialNodePaths = [`${cliDir}/node`, `${cliDir}/bin/node`];
for (const nodePath of potentialNodePaths) {
try {
if (statSync(nodePath).isFile()) {
const stats = statSync(nodePath);
if (stats.mode & 0o111) {
console.log(
`[ACP] Found Node.js executable in CLI directory at: ${nodePath}`,
);
return nodePath;
}
}
} catch (_error) {
// File doesn't exist, continue
continue;
}
}
console.log(`[ACP] Could not determine Node.js path for CLI: ${cliPath}`);
return null;
}
/**
* Connect to ACP backend
*
@@ -185,14 +117,21 @@ export class AcpConnection {
// Handle various Node.js version managers (nvm, n, manual installations)
if (cliPath.includes('/qwen') && !isWindows) {
// Try to determine the correct node executable for this qwen installation
const nodePath = this.determineNodePathForCli(cliPath);
if (nodePath) {
spawnCommand = nodePath;
const nodePathResult = determineNodePathForCli(cliPath);
if (nodePathResult.path) {
spawnCommand = nodePathResult.path;
spawnArgs = [cliPath, '--experimental-acp', ...extraArgs];
} else {
// Fallback to direct execution
spawnCommand = cliPath;
spawnArgs = ['--experimental-acp', ...extraArgs];
// Log any error for debugging
if (nodePathResult.error) {
console.warn(
`[ACP] Node.js path detection warning: ${nodePathResult.error}`,
);
}
}
} else {
spawnCommand = cliPath;

View File

@@ -18,7 +18,7 @@ import type {
AcpSessionUpdate,
AcpPermissionRequest,
} from '../constants/acpTypes.js';
import { CLIENT_METHODS } from './schema.js';
import { CLIENT_METHODS } from '../constants/acpSchema.js';
import type {
PendingRequest,
AcpConnectionCallbacks,

View File

@@ -16,7 +16,7 @@ import type {
AcpNotification,
AcpResponse,
} from '../constants/acpTypes.js';
import { AGENT_METHODS, CUSTOM_METHODS } from './schema.js';
import { AGENT_METHODS, CUSTOM_METHODS } from '../constants/acpSchema.js';
import type { PendingRequest } from './connectionTypes.js';
import type { ChildProcess } from 'child_process';

View File

@@ -1,60 +0,0 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* ACP (Agent Communication Protocol) Method Definitions
*
* This file defines the protocol methods for communication between
* the VSCode extension (Client) and the qwen CLI (Agent/Server).
*/
/**
* Methods that the Agent (CLI) implements and receives from Client (VSCode)
*
* Status in qwen CLI:
* ✅ initialize - Protocol initialization
* ✅ authenticate - User authentication
* ✅ session/new - Create new session
* ✅ session/load - Load existing session (v0.2.4+)
* ✅ session/list - List available sessions (v0.2.4+)
* ✅ session/prompt - Send user message to agent
* ✅ session/cancel - Cancel current generation
* ✅ session/save - Save current session
*/
export const AGENT_METHODS = {
authenticate: 'authenticate',
initialize: 'initialize',
session_cancel: 'session/cancel',
session_list: 'session/list',
session_load: 'session/load',
session_new: 'session/new',
session_prompt: 'session/prompt',
session_save: 'session/save',
} as const;
/**
* Methods that the Client (VSCode) implements and receives from Agent (CLI)
*
* Status in VSCode extension:
* ✅ fs/read_text_file - Read file content
* ✅ fs/write_text_file - Write file content
* ✅ session/request_permission - Request user permission for tool execution
* ✅ session/update - Stream session updates (notification)
*/
export const CLIENT_METHODS = {
fs_read_text_file: 'fs/read_text_file',
fs_write_text_file: 'fs/write_text_file',
session_request_permission: 'session/request_permission',
session_update: 'session/update',
} as const;
/**
* Custom methods (not in standard ACP protocol)
* These are VSCode extension specific extensions
*/
export const CUSTOM_METHODS = {
session_list: 'session/list',
} as const;