@@ -539,81 +539,13 @@ export class Remote {
539539 const inbox = await Inbox . create ( workspace , workspaceClient , this . logger ) ;
540540 disposables . push ( inbox ) ;
541541
542- // Wait for the agent to connect.
543- if ( agent . status === "connecting" ) {
544- this . logger . info ( `Waiting for ${ workspaceName } /${ agent . name } ...` ) ;
545- const updatedAgent = await this . waitForAgentWithProgress (
546- monitor ,
547- agent ,
548- `Waiting for agent ${ agent . name } to connect...` ,
549- ( foundAgent ) => foundAgent . status !== "connecting" ,
550- ) ;
551- agent = updatedAgent ;
552- this . logger . info ( `Agent ${ agent . name } status is now` , agent . status ) ;
553- }
554-
555- // Make sure the agent is connected.
556- if ( agent . status !== "connected" ) {
557- const result = await this . vscodeProposed . window . showErrorMessage (
558- `${ workspaceName } /${ agent . name } ${ agent . status } ` ,
559- {
560- useCustom : true ,
561- modal : true ,
562- detail : `The ${ agent . name } agent failed to connect. Try restarting your workspace.` ,
563- } ,
564- ) ;
565- if ( ! result ) {
566- await this . closeRemote ( ) ;
567- return ;
568- }
569- await this . reloadWindow ( ) ;
570- return ;
571- }
572-
573- if ( agent . lifecycle_state === "starting" ) {
574- const isBlocking = agent . scripts . some (
575- ( script ) => script . start_blocks_login ,
576- ) ;
577- if ( isBlocking ) {
578- this . logger . info (
579- `Waiting for ${ workspaceName } /${ agent . name } startup...` ,
580- ) ;
581-
582- let terminalSession : TerminalSession | undefined ;
583- let socket : OneWayWebSocket < WorkspaceAgentLog [ ] > | undefined ;
584- try {
585- terminalSession = new TerminalSession ( "Agent Log" ) ;
586- const { socket : agentSocket , completion : logsCompletion } =
587- await streamAgentLogs (
588- workspaceClient ,
589- terminalSession . writeEmitter ,
590- agent ,
591- ) ;
592- socket = agentSocket ;
593-
594- const agentStatePromise = this . waitForAgentWithProgress (
595- monitor ,
596- agent ,
597- `Waiting for agent ${ agent . name } startup scripts...` ,
598- ( foundAgent ) => foundAgent . lifecycle_state !== "starting" ,
599- ) ;
600-
601- // Race between logs completion and agent state change
602- const updatedAgent = await Promise . race ( [
603- agentStatePromise ,
604- logsCompletion . then ( ( ) => agentStatePromise ) ,
605- ] ) ;
606- agent = updatedAgent ;
607- this . logger . info (
608- `Agent ${ agent . name } lifecycle state is now` ,
609- agent . lifecycle_state ,
610- ) ;
611- } finally {
612- terminalSession ?. dispose ( ) ;
613- socket ?. close ( ) ;
614- }
615- }
616- }
542+ // Ensure agent is ready by handling all status and lifecycle states
543+ agent = await this . ensureAgentReady (
544+ monitor ,
545+ agent ,
546+ workspaceName ,
547+ workspaceClient ,
548+ ) ;
617549
618550 const logDir = this . getLogDir ( featureSet ) ;
619551
@@ -740,6 +672,139 @@ export class Remote {
740672 return ` --log-dir ${ escapeCommandArg ( logDir ) } -v` ;
741673 }
742674
675+ /**
676+ * Ensures agent is ready to connect by handling all status and lifecycle states.
677+ * Throws an error if the agent cannot be made ready.
678+ */
679+ private async ensureAgentReady (
680+ monitor : WorkspaceMonitor ,
681+ agent : WorkspaceAgent ,
682+ workspaceName : string ,
683+ workspaceClient : CoderApi ,
684+ ) : Promise < WorkspaceAgent > {
685+ let currentAgent = agent ;
686+
687+ while (
688+ currentAgent . status !== "connected" ||
689+ currentAgent . lifecycle_state !== "ready"
690+ ) {
691+ switch ( currentAgent . status ) {
692+ case "connecting" :
693+ this . logger . info ( `Waiting for agent ${ currentAgent . name } ...` ) ;
694+ currentAgent = await this . waitForAgentWithProgress (
695+ monitor ,
696+ currentAgent ,
697+ `Waiting for agent ${ currentAgent . name } to connect...` ,
698+ ( foundAgent ) => foundAgent . status !== "connecting" ,
699+ ) ;
700+ this . logger . info (
701+ `Agent ${ currentAgent . name } status is now` ,
702+ currentAgent . status ,
703+ ) ;
704+ continue ;
705+
706+ case "connected" :
707+ // Agent connected, now handle lifecycle state
708+ break ;
709+
710+ case "disconnected" :
711+ case "timeout" :
712+ throw new Error (
713+ `${ workspaceName } /${ currentAgent . name } ${ currentAgent . status } ` ,
714+ ) ;
715+
716+ default :
717+ throw new Error (
718+ `${ workspaceName } /${ currentAgent . name } unknown status: ${ currentAgent . status } ` ,
719+ ) ;
720+ }
721+
722+ // Handle agent lifecycle state (only when status is "connected")
723+ switch ( currentAgent . lifecycle_state ) {
724+ case "ready" :
725+ return currentAgent ;
726+
727+ case "starting" : {
728+ const isBlocking = currentAgent . scripts . some (
729+ ( script ) => script . start_blocks_login ,
730+ ) ;
731+ if ( ! isBlocking ) {
732+ return currentAgent ;
733+ }
734+
735+ const logMsg = `Waiting for agent ${ currentAgent . name } startup scripts...` ;
736+ this . logger . info ( logMsg ) ;
737+
738+ let terminalSession : TerminalSession | undefined ;
739+ let socket : OneWayWebSocket < WorkspaceAgentLog [ ] > | undefined ;
740+ try {
741+ terminalSession = new TerminalSession ( "Agent Log" ) ;
742+ const { socket : agentSocket , completion : logsCompletion } =
743+ await streamAgentLogs (
744+ workspaceClient ,
745+ terminalSession . writeEmitter ,
746+ currentAgent ,
747+ ) ;
748+ socket = agentSocket ;
749+
750+ const agentStatePromise = this . waitForAgentWithProgress (
751+ monitor ,
752+ currentAgent ,
753+ logMsg ,
754+ ( foundAgent ) => foundAgent . lifecycle_state !== "starting" ,
755+ ) ;
756+
757+ // Race between logs completion and agent state change
758+ currentAgent = await Promise . race ( [
759+ agentStatePromise ,
760+ logsCompletion . then ( ( ) => agentStatePromise ) ,
761+ ] ) ;
762+ this . logger . info (
763+ `Agent ${ currentAgent . name } lifecycle state is now` ,
764+ currentAgent . lifecycle_state ,
765+ ) ;
766+ } finally {
767+ terminalSession ?. dispose ( ) ;
768+ socket ?. close ( ) ;
769+ }
770+ continue ;
771+ }
772+
773+ case "created" :
774+ this . logger . info (
775+ `Waiting for ${ workspaceName } /${ currentAgent . name } to start...` ,
776+ ) ;
777+ currentAgent = await this . waitForAgentWithProgress (
778+ monitor ,
779+ currentAgent ,
780+ `Waiting for agent ${ currentAgent . name } to start...` ,
781+ ( foundAgent ) => foundAgent . lifecycle_state !== "created" ,
782+ ) ;
783+ this . logger . info (
784+ `Agent ${ currentAgent . name } lifecycle state is now` ,
785+ currentAgent . lifecycle_state ,
786+ ) ;
787+ continue ;
788+
789+ case "off" :
790+ case "start_error" :
791+ case "start_timeout" :
792+ case "shutdown_error" :
793+ case "shutdown_timeout" :
794+ case "shutting_down" :
795+ throw new Error (
796+ `${ workspaceName } /${ currentAgent . name } lifecycle state: ${ currentAgent . lifecycle_state } ` ,
797+ ) ;
798+
799+ default :
800+ throw new Error (
801+ `${ workspaceName } /${ currentAgent . name } unknown lifecycle state: ${ currentAgent . lifecycle_state } ` ,
802+ ) ;
803+ }
804+ }
805+ return currentAgent ;
806+ }
807+
743808 /**
744809 * Waits for an agent condition with progress notification.
745810 */
@@ -749,7 +814,7 @@ export class Remote {
749814 progressTitle : string ,
750815 checker : ( agent : WorkspaceAgent ) => boolean ,
751816 ) : Promise < WorkspaceAgent > {
752- const foundAgent = await vscode . window . withProgress (
817+ const foundAgent = await this . vscodeProposed . window . withProgress (
753818 {
754819 title : progressTitle ,
755820 location : vscode . ProgressLocation . Notification ,
@@ -770,6 +835,10 @@ export class Remote {
770835 ) : Promise < WorkspaceAgent > {
771836 return new Promise < WorkspaceAgent > ( ( resolve , reject ) => {
772837 const updateEvent = monitor . onChange . event ( ( workspace ) => {
838+ if ( workspace . latest_build . status !== "running" ) {
839+ const workspaceName = createWorkspaceIdentifier ( workspace ) ;
840+ reject ( new Error ( `Workspace ${ workspaceName } is not running.` ) ) ;
841+ }
773842 try {
774843 const agents = extractAgents ( workspace . latest_build . resources ) ;
775844 const foundAgent = agents . find ( ( a ) => a . id === agent . id ) ;
@@ -778,8 +847,7 @@ export class Remote {
778847 `Agent ${ agent . name } not found in workspace resources` ,
779848 ) ;
780849 }
781- const result = checker ( foundAgent ) ;
782- if ( result !== undefined ) {
850+ if ( checker ( foundAgent ) ) {
783851 updateEvent . dispose ( ) ;
784852 resolve ( foundAgent ) ;
785853 }
0 commit comments