@@ -18,6 +18,7 @@ import * as fs from 'fs-extra';
1818import * as os from 'os' ;
1919import * as path from '../../platform/vscode-path/path' ;
2020import { generateUuid } from '../../platform/common/uuid' ;
21+ import { DeepnoteServerStartupError , DeepnoteServerTimeoutError } from '../../platform/errors/deepnoteKernelErrors' ;
2122
2223/**
2324 * Lock file data structure for tracking server ownership
@@ -42,6 +43,8 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
4243 private readonly sessionId : string = generateUuid ( ) ;
4344 // Directory for lock files
4445 private readonly lockFileDir : string = path . join ( os . tmpdir ( ) , 'vscode-deepnote-locks' ) ;
46+ // Track server output for error reporting
47+ private readonly serverOutputByFile : Map < string , { stdout : string ; stderr : string } > = new Map ( ) ;
4548
4649 constructor (
4750 @inject ( IProcessServiceFactory ) private readonly processServiceFactory : IProcessServiceFactory ,
@@ -118,12 +121,9 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
118121
119122 Cancellation . throwIfCanceled ( token ) ;
120123
121- // Ensure toolkit is installed
124+ // Ensure toolkit is installed (will throw typed errors on failure)
122125 logger . info ( `Ensuring deepnote-toolkit is installed for ${ fileKey } ...` ) ;
123- const installed = await this . toolkitInstaller . ensureInstalled ( interpreter , deepnoteFileUri , token ) ;
124- if ( ! installed ) {
125- throw new Error ( 'Failed to install deepnote-toolkit. Please check the output for details.' ) ;
126- }
126+ await this . toolkitInstaller . ensureInstalled ( interpreter , deepnoteFileUri , token ) ;
127127
128128 Cancellation . throwIfCanceled ( token ) ;
129129
@@ -197,15 +197,27 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
197197 const disposables : IDisposable [ ] = [ ] ;
198198 this . disposablesByFile . set ( fileKey , disposables ) ;
199199
200+ // Initialize output tracking for error reporting
201+ this . serverOutputByFile . set ( fileKey , { stdout : '' , stderr : '' } ) ;
202+
200203 // Monitor server output
201204 serverProcess . out . onDidChange (
202205 ( output ) => {
206+ const outputTracking = this . serverOutputByFile . get ( fileKey ) ;
203207 if ( output . source === 'stdout' ) {
204208 logger . trace ( `Deepnote server (${ fileKey } ): ${ output . out } ` ) ;
205209 this . outputChannel . appendLine ( output . out ) ;
210+ if ( outputTracking ) {
211+ // Keep last 5000 characters of output for error reporting
212+ outputTracking . stdout = ( outputTracking . stdout + output . out ) . slice ( - 5000 ) ;
213+ }
206214 } else if ( output . source === 'stderr' ) {
207215 logger . warn ( `Deepnote server stderr (${ fileKey } ): ${ output . out } ` ) ;
208216 this . outputChannel . appendLine ( output . out ) ;
217+ if ( outputTracking ) {
218+ // Keep last 5000 characters of error output for error reporting
219+ outputTracking . stderr = ( outputTracking . stderr + output . out ) . slice ( - 5000 ) ;
220+ }
209221 }
210222 } ,
211223 this ,
@@ -228,13 +240,35 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
228240 try {
229241 const serverReady = await this . waitForServer ( serverInfo , 120000 , token ) ;
230242 if ( ! serverReady ) {
243+ const output = this . serverOutputByFile . get ( fileKey ) ;
231244 await this . stopServerImpl ( deepnoteFileUri ) ;
232- throw new Error ( 'Deepnote server failed to start within timeout period' ) ;
245+
246+ throw new DeepnoteServerTimeoutError ( serverInfo . url , 120000 , output ?. stderr || undefined ) ;
233247 }
234248 } catch ( error ) {
235- // Clean up leaked server before rethrowing
249+ // If this is already a DeepnoteKernelError, clean up and rethrow it
250+ if ( error instanceof DeepnoteServerTimeoutError || error instanceof DeepnoteServerStartupError ) {
251+ await this . stopServerImpl ( deepnoteFileUri ) ;
252+ throw error ;
253+ }
254+
255+ // Capture output BEFORE cleaning up (stopServerImpl deletes it)
256+ const output = this . serverOutputByFile . get ( fileKey ) ;
257+ const capturedStdout = output ?. stdout || '' ;
258+ const capturedStderr = output ?. stderr || '' ;
259+
260+ // Clean up leaked server after capturing output
236261 await this . stopServerImpl ( deepnoteFileUri ) ;
237- throw error ;
262+
263+ // Wrap in a generic server startup error with captured output
264+ throw new DeepnoteServerStartupError (
265+ interpreter . uri . fsPath ,
266+ port ,
267+ 'unknown' ,
268+ capturedStdout ,
269+ capturedStderr ,
270+ error instanceof Error ? error : undefined
271+ ) ;
238272 }
239273
240274 logger . info ( `Deepnote server started successfully at ${ url } for ${ fileKey } ` ) ;
@@ -283,6 +317,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
283317 serverProcess . proc ?. kill ( ) ;
284318 this . serverProcesses . delete ( fileKey ) ;
285319 this . serverInfos . delete ( fileKey ) ;
320+ this . serverOutputByFile . delete ( fileKey ) ;
286321 this . outputChannel . appendLine ( `Deepnote server stopped for ${ fileKey } ` ) ;
287322
288323 // Clean up lock file after stopping the server
@@ -403,6 +438,7 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
403438 this . serverInfos . clear ( ) ;
404439 this . disposablesByFile . clear ( ) ;
405440 this . pendingOperations . clear ( ) ;
441+ this . serverOutputByFile . clear ( ) ;
406442
407443 logger . info ( 'DeepnoteServerStarter disposed successfully' ) ;
408444 }
0 commit comments