Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions apps/vscode/src/connection/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,7 @@ export class ConnectionManager extends EventEmitter {
return;
}

const wsUrl = this.configManager.getWebSocketUrl();
this.logger.output(`${icons.connecting} Connecting to ${wsUrl}...`);
this.logger.output(`${icons.connecting} Connecting...`);

this.updateConnectionStatus({
state: ConnectionState.CONNECTING,
Expand Down Expand Up @@ -291,7 +290,10 @@ export class ConnectionManager extends EventEmitter {
}

private async _connectClientWithRetries(): Promise<void> {
this.updateConnectionStatus({ state: ConnectionState.CONNECTING });
this.updateConnectionStatus({
state: ConnectionState.CONNECTING,
progressMessage: undefined
});

// Clean up any previous client
if (this.client) {
Expand All @@ -304,6 +306,9 @@ export class ConnectionManager extends EventEmitter {

while (attempts < maxAttempts && this.connectionStatus.state === 'connecting') {
try {
if (attempts > 0) {
this.updateConnectionStatus({ progressMessage: 'Attempting connection...' });
}
this.client = this.createClient();
await this.client.connect(5000);
this.onConnectionEstablished();
Expand All @@ -318,18 +323,19 @@ export class ConnectionManager extends EventEmitter {
this.client = undefined;
}

if (!this.retryingMessageShown) {
this.logger.output(`${icons.loading} Retrying connection...`);
this.retryingMessageShown = true;
}

if (attempts >= maxAttempts) {
const msg = error instanceof Error ? error.message : String(error);
this.logger.output(`${icons.error} Connection failed after ${maxAttempts} attempts: ${msg}`);
throw new Error(`Connection failed after ${maxAttempts} attempts: ${msg}`);
}

if (this.connectionStatus.state === 'connecting') {
const delayMs = this.getBackoffDelayMs(attempts - 1);
const delaySec = Math.round(delayMs / 1000);
this.logger.output(`${icons.info} Connection attempt ${attempts} failed, waiting ${delaySec}s...`);
this.updateConnectionStatus({
progressMessage: `Waiting ${delaySec}s before next attempt`
});
await this.delay(delayMs);
}
}
Expand All @@ -355,7 +361,7 @@ export class ConnectionManager extends EventEmitter {

const executablePath = this.engineInstaller!.getExecutablePath();

this.logger.output(`${icons.launch} Starting local engine at ${config.local.host}:${config.local.port}`);
this.logger.output(`${icons.launch} Starting local server at ${config.local.host}:${config.local.port}`);

if (this.dapServer) {
this.dapServer.removeAllListeners();
Expand All @@ -376,11 +382,11 @@ export class ConnectionManager extends EventEmitter {
await this.dapServer.startServer(executablePath, args);

this.dapServer.on('terminated', (details) => {
this.logger.output(`${icons.warning} Local engine terminated (code=${details.code}, signal=${details.signal})`);
this.logger.output(`${icons.warning} Local server terminated (code=${details.code}, signal=${details.signal})`);
this.handleConnectionLoss();
});

this.logger.output(`${icons.success} Local engine started and ready`);
this.logger.output(`${icons.success} Local server started`);
} catch (error) {
this.updateConnectionStatus({ state: ConnectionState.DISCONNECTED });
throw error;
Expand All @@ -392,7 +398,8 @@ export class ConnectionManager extends EventEmitter {
state: ConnectionState.CONNECTED,
lastConnected: new Date(),
lastError: undefined,
retryAttempt: 0
retryAttempt: 0,
progressMessage: undefined
});

this.resetRetryState();
Expand Down Expand Up @@ -436,6 +443,7 @@ export class ConnectionManager extends EventEmitter {
}

const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.output(`${icons.error} ${errorMessage}`);

const shouldReconnect = !this.isManualDisconnect &&
this.connectionStatus.hasCredentials &&
Expand Down Expand Up @@ -477,15 +485,18 @@ export class ConnectionManager extends EventEmitter {
return;
}

if (this.connectionStatus.retryAttempt === 0) {
this.logger.output(`${icons.info} Waiting for connection...`);
}

const delayMs = this.getBackoffDelayMs(this.connectionStatus.retryAttempt);
const delaySec = Math.round(delayMs / 1000);
this.logger.output(`${icons.info} Waiting ${delaySec}s before retrying...`);
this.updateConnectionStatus({
progressMessage: `Waiting ${delaySec}s before next attempt`
});
this.reconnectTimeout = setTimeout(async () => {
if (this.connectionStatus.state === 'connecting' && !this.isManualDisconnect && !this.isDisposing) {
this.logger.output(`${icons.connecting} Reconnecting...`);
this.updateConnectionStatus({
retryAttempt: this.connectionStatus.retryAttempt + 1
retryAttempt: this.connectionStatus.retryAttempt + 1,
progressMessage: 'Attempting connection...'
});

try {
Expand Down Expand Up @@ -647,7 +658,7 @@ export class ConnectionManager extends EventEmitter {
if (this.dapServer) {
this.updateConnectionStatus({ state: ConnectionState.STOPPING_ENGINE });

this.logger.output(`${icons.stop} Stopping local engine...`);
this.logger.output(`${icons.stop} Stopping local server...`);

this.dapServer.removeAllListeners();
this.dapServer.stopServer();
Expand Down
30 changes: 18 additions & 12 deletions apps/vscode/src/connection/engine-installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ interface PlatformInfo {
}

export class EngineInstaller {
private static readonly GITHUB_OWNER = 'rocketride-ai';
private static readonly GITHUB_OWNER = 'rocketride-org';
private static readonly GITHUB_REPO = 'rocketride-server';

private readonly engineDir: string;
Expand Down Expand Up @@ -114,7 +114,7 @@ export class EngineInstaller {
token?: vscode.CancellationToken,
githubToken?: string
): Promise<string> {
this.logger.output(`${icons.info} Engine not found, downloading latest release...`);
this.logger.output(`${icons.info} Server not found locally, downloading...`);

// Fetch latest release info from GitHub
progress?.report({ message: 'Fetching latest release info...' });
Expand All @@ -138,7 +138,7 @@ export class EngineInstaller {
fs.mkdirSync(this.engineDir, { recursive: true });

// Extract
progress?.report({ message: 'Extracting engine...' });
progress?.report({ message: 'Extracting server...' });
await this.extractArchive(tmpPath, this.engineDir);

// Set executable permissions on Unix
Expand All @@ -151,8 +151,8 @@ export class EngineInstaller {
);
}

this.logger.output(`${icons.success} Engine installed successfully at ${this.engineDir}`);
progress?.report({ message: 'Engine ready!' });
this.logger.output(`${icons.success} Server installed at ${this.engineDir}`);
progress?.report({ message: 'Server ready!' });

return this.getExecutablePath();
} finally {
Expand All @@ -171,18 +171,24 @@ export class EngineInstaller {
this.throwIfCancelled(token);

const octokit = await this.createOctokit(githubToken);
const { data } = await octokit.repos.getLatestRelease({
const { data } = await octokit.repos.listReleases({
owner: EngineInstaller.GITHUB_OWNER,
repo: EngineInstaller.GITHUB_REPO
repo: EngineInstaller.GITHUB_REPO,
per_page: 1
});

if (!data.tag_name || !data.assets || data.assets.length === 0) {
throw new Error('Invalid release data from GitHub API');
if (data.length === 0) {
throw new Error('No releases found on GitHub');
}

const release = data[0];
if (!release.tag_name || !release.assets || release.assets.length === 0) {
throw new Error(`Release ${release.tag_name} has no assets`);
}

return {
tag_name: data.tag_name,
assets: data.assets.map(a => ({
tag_name: release.tag_name,
assets: release.assets.map(a => ({
id: a.id,
name: a.name,
browser_download_url: a.browser_download_url,
Expand All @@ -209,7 +215,7 @@ export class EngineInstaller {

private getPlatformAssetName(tagName: string): string {
const info = this.getPlatformInfo();
return `engine-${tagName}-${info.name}.${info.ext}`;
return `rocketride-${tagName}-${info.name}.${info.ext}`;
}

private findPlatformAsset(release: ReleaseInfo): ReleaseAsset {
Expand Down
74 changes: 44 additions & 30 deletions apps/vscode/src/providers/SidebarConnectionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,17 @@ export class SidebarConnectionProvider implements vscode.TreeDataProvider<Connec
items.push(errorItem);
}

// Show progress message (e.g. download progress) during connecting states
if (connectionState.progressMessage && this.isConnectingState(connectionState.state)) {
const progressItem = new ConnectionItem(
connectionState.progressMessage,
vscode.TreeItemCollapsibleState.None,
'progress-detail'
);
items.push(progressItem);
// Show detail line during connecting states: progressMessage or state-based fallback
if (this.isConnectingState(connectionState.state)) {
const detail = connectionState.progressMessage || this.getStateDetail(connectionState);
if (detail) {
const progressItem = new ConnectionItem(
detail,
vscode.TreeItemCollapsibleState.None,
'progress-detail'
);
items.push(progressItem);
}
}

// Blank line after status/retry info
Expand Down Expand Up @@ -352,6 +355,27 @@ export class SidebarConnectionProvider implements vscode.TreeDataProvider<Connec
status === ConnectionState.STOPPING_ENGINE;
}

/**
* Returns a detail string describing what is happening in the current state
*/
private getStateDetail(connectionState: ConnectionStatus): string {
switch (connectionState.state) {
case 'downloading-engine':
return 'Downloading server...';
case 'starting-engine':
return 'Starting server...';
case 'connecting':
if (connectionState.retryAttempt > 0) {
return 'Retrying...';
}
return 'Connecting to server...';
case 'stopping-engine':
return 'Stopping server...';
default:
return '';
}
}

/**
* Generates status label with animated dots for connecting states
*/
Expand All @@ -370,29 +394,19 @@ export class SidebarConnectionProvider implements vscode.TreeDataProvider<Connec

switch (connectionState.state) {
case 'connected':
return `Connected (${connectionState.connectionMode})`;

case 'downloading-engine': {
const downloadingDots = getConnectingDots(connectionState.retryAttempt);
return `Downloading Engine${downloadingDots}`;
}

case 'starting-engine': {
const startingDots = getConnectingDots(connectionState.retryAttempt);
return `Starting Engine${startingDots}`;
}
return 'Connected';

case 'connecting': {
case 'downloading-engine':
case 'starting-engine':
case 'connecting':
case 'stopping-engine': {
const connectingDots = getConnectingDots(connectionState.retryAttempt);
return `Connecting${connectingDots}`;
}

case 'stopping-engine':
return `Stopping Engine...`;

case 'disconnected':
default:
return `Disconnected`;
return 'Disconnected';
}
}

Expand Down Expand Up @@ -425,26 +439,26 @@ export class SidebarConnectionProvider implements vscode.TreeDataProvider<Connec
return `Successfully connected to ${config.hostUrl} in ${connectionState.connectionMode} mode`;

case 'downloading-engine':
return 'Downloading engine from GitHub releases...';
return 'Downloading RocketRide server from GitHub releases...';

case 'starting-engine':
return 'Starting local engine process...';
return 'Starting local RocketRide server...';

case 'connecting':
if (connectionState.connectionMode === 'local') {
if (connectionState.retryAttempt > 0) {
return `Engine started, retrying WebSocket connection (attempt ${connectionState.retryAttempt}/${connectionState.maxRetryAttempts})...`;
return 'Server started, retrying connection...';
}
return 'Engine started, connecting to WebSocket...';
return 'Server started, connecting...';
} else {
if (connectionState.retryAttempt > 0) {
return `Retrying connection (attempt ${connectionState.retryAttempt}/${connectionState.maxRetryAttempts})...`;
return 'Retrying connection...';
}
return 'Connecting to server...';
}

case 'stopping-engine':
return 'Stopping local engine process...';
return 'Stopping local RocketRide server...';

case 'disconnected':
default:
Expand Down
Loading
Loading