Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit35387f2

Browse files
authored
Add workspace and agent state machines with improved progress tracking (#627)
Introduces state machines to manage workspace and agent lifecycle transitionsduring connection. Improves user experience with clearer progress messages,enhanced websocket-based log streaming, and proper handling of blocking startup scripts. Previously, the extension would connect before startup scriptscompleted, leaving users waiting with no indication. Now it waits for scripts tofinish and shows clear progress throughout the connection process.Closes#626
1 parenta1ad85e commit35387f2

File tree

10 files changed

+654
-431
lines changed

10 files changed

+654
-431
lines changed

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
##Unreleased
44

5+
###Changed
6+
7+
- Improved workspace connection progress messages and enhanced the workspace build terminal
8+
with better log streaming. The extension now also waits for blocking startup scripts to
9+
complete before connecting, providing clear progress indicators during the wait.
10+
511
##[v1.11.3](https://github.com/coder/vscode-coder/releases/tag/v1.11.3) 2025-10-22
612

713
###Fixed

‎src/api/coderApi.ts‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
typeProvisionerJobLog,
1212
typeWorkspace,
1313
typeWorkspaceAgent,
14+
typeWorkspaceAgentLog,
1415
}from"coder/site/src/api/typesGenerated";
1516
import*asvscodefrom"vscode";
1617
import{typeClientOptions,typeCloseEvent,typeErrorEvent}from"ws";
@@ -109,18 +110,42 @@ export class CoderApi extends Api {
109110
logs:ProvisionerJobLog[],
110111
options?:ClientOptions,
111112
)=>{
113+
returnthis.watchLogs<ProvisionerJobLog>(
114+
`/api/v2/workspacebuilds/${buildId}/logs`,
115+
logs,
116+
options,
117+
);
118+
};
119+
120+
watchWorkspaceAgentLogs=async(
121+
agentId:string,
122+
logs:WorkspaceAgentLog[],
123+
options?:ClientOptions,
124+
)=>{
125+
returnthis.watchLogs<WorkspaceAgentLog[]>(
126+
`/api/v2/workspaceagents/${agentId}/logs`,
127+
logs,
128+
options,
129+
);
130+
};
131+
132+
privateasyncwatchLogs<TData>(
133+
apiRoute:string,
134+
logs:{id:number}[],
135+
options?:ClientOptions,
136+
){
112137
constsearchParams=newURLSearchParams({follow:"true"});
113138
constlastLog=logs.at(-1);
114139
if(lastLog){
115140
searchParams.append("after",lastLog.id.toString());
116141
}
117142

118-
returnthis.createWebSocket<ProvisionerJobLog>({
119-
apiRoute:`/api/v2/workspacebuilds/${buildId}/logs`,
143+
returnthis.createWebSocket<TData>({
144+
apiRoute,
120145
searchParams,
121146
options,
122147
});
123-
};
148+
}
124149

125150
privateasynccreateWebSocket<TData=unknown>(
126151
configs:Omit<OneWayWebSocketInit,"location">,

‎src/api/workspace.ts‎

Lines changed: 75 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import{spawn}from"child_process";
21
import{typeApi}from"coder/site/src/api/api";
3-
import{typeWorkspace}from"coder/site/src/api/typesGenerated";
2+
import{
3+
typeWorkspaceAgentLog,
4+
typeProvisionerJobLog,
5+
typeWorkspace,
6+
typeWorkspaceAgent,
7+
}from"coder/site/src/api/typesGenerated";
8+
import{spawn}from"node:child_process";
49
import*asvscodefrom"vscode";
510

611
import{typeFeatureSet}from"../featureSet";
712
import{getGlobalFlags}from"../globalFlags";
813
import{escapeCommandArg}from"../util";
14+
import{typeOneWayWebSocket}from"../websocket/oneWayWebSocket";
915

1016
import{errToStr,createWorkspaceIdentifier}from"./api-helper";
1117
import{typeCoderApi}from"./coderApi";
@@ -36,35 +42,33 @@ export async function startWorkspaceIfStoppedOrFailed(
3642
createWorkspaceIdentifier(workspace),
3743
];
3844
if(featureSet.buildReason){
39-
startArgs.push(...["--reason","vscode_connection"]);
45+
startArgs.push("--reason","vscode_connection");
4046
}
4147

4248
// { shell: true } requires one shell-safe command string, otherwise we lose all escaping
4349
constcmd=`${escapeCommandArg(binPath)}${startArgs.join(" ")}`;
4450
conststartProcess=spawn(cmd,{shell:true});
4551

4652
startProcess.stdout.on("data",(data:Buffer)=>{
47-
data
53+
constlines=data
4854
.toString()
4955
.split(/\r*\n/)
50-
.forEach((line:string)=>{
51-
if(line!==""){
52-
writeEmitter.fire(line.toString()+"\r\n");
53-
}
54-
});
56+
.filter((line)=>line!=="");
57+
for(constlineoflines){
58+
writeEmitter.fire(line.toString()+"\r\n");
59+
}
5560
});
5661

5762
letcapturedStderr="";
5863
startProcess.stderr.on("data",(data:Buffer)=>{
59-
data
64+
constlines=data
6065
.toString()
6166
.split(/\r*\n/)
62-
.forEach((line:string)=>{
63-
if(line!==""){
64-
writeEmitter.fire(line.toString()+"\r\n");
65-
capturedStderr+=line.toString()+"\n";
66-
}
67-
});
67+
.filter((line)=>line!=="");
68+
for(constlineoflines){
69+
writeEmitter.fire(line.toString()+"\r\n");
70+
capturedStderr+=line.toString()+"\n";
71+
}
6872
});
6973

7074
startProcess.on("close",(code:number)=>{
@@ -82,51 +86,72 @@ export async function startWorkspaceIfStoppedOrFailed(
8286
}
8387

8488
/**
85-
* Wait for the latest build to finish while streaming logs to the emitter.
86-
*
87-
* Once completed, fetch the workspace again and return it.
89+
* Streams build logs to the emitter in real-time.
90+
* Returns the websocket for lifecycle management.
8891
*/
89-
exportasyncfunctionwaitForBuild(
92+
exportasyncfunctionstreamBuildLogs(
9093
client:CoderApi,
9194
writeEmitter:vscode.EventEmitter<string>,
9295
workspace:Workspace,
93-
):Promise<Workspace>{
94-
// This fetches the initial bunch of logs.
95-
constlogs=awaitclient.getWorkspaceBuildLogs(workspace.latest_build.id);
96-
logs.forEach((log)=>writeEmitter.fire(log.output+"\r\n"));
97-
96+
):Promise<OneWayWebSocket<ProvisionerJobLog>>{
9897
constsocket=awaitclient.watchBuildLogsByBuildId(
9998
workspace.latest_build.id,
100-
logs,
99+
[],
101100
);
102101

103-
awaitnewPromise<void>((resolve,reject)=>{
104-
socket.addEventListener("message",(data)=>{
105-
if(data.parseError){
106-
writeEmitter.fire(
107-
errToStr(data.parseError,"Failed to parse message")+"\r\n",
108-
);
109-
}else{
110-
writeEmitter.fire(data.parsedMessage.output+"\r\n");
111-
}
112-
});
102+
socket.addEventListener("message",(data)=>{
103+
if(data.parseError){
104+
writeEmitter.fire(
105+
errToStr(data.parseError,"Failed to parse message")+"\r\n",
106+
);
107+
}else{
108+
writeEmitter.fire(data.parsedMessage.output+"\r\n");
109+
}
110+
});
111+
112+
socket.addEventListener("error",(error)=>{
113+
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
114+
writeEmitter.fire(
115+
`Error watching workspace build logs on${baseUrlRaw}:${errToStr(error,"no further details")}\r\n`,
116+
);
117+
});
118+
119+
socket.addEventListener("close",()=>{
120+
writeEmitter.fire("Build complete\r\n");
121+
});
122+
123+
returnsocket;
124+
}
113125

114-
socket.addEventListener("error",(error)=>{
115-
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
116-
returnreject(
117-
newError(
118-
`Failed to watch workspace build on${baseUrlRaw}:${errToStr(error,"no further details")}`,
119-
),
126+
/**
127+
* Streams agent logs to the emitter in real-time.
128+
* Returns the websocket for lifecycle management.
129+
*/
130+
exportasyncfunctionstreamAgentLogs(
131+
client:CoderApi,
132+
writeEmitter:vscode.EventEmitter<string>,
133+
agent:WorkspaceAgent,
134+
):Promise<OneWayWebSocket<WorkspaceAgentLog[]>>{
135+
constsocket=awaitclient.watchWorkspaceAgentLogs(agent.id,[]);
136+
137+
socket.addEventListener("message",(data)=>{
138+
if(data.parseError){
139+
writeEmitter.fire(
140+
errToStr(data.parseError,"Failed to parse message")+"\r\n",
120141
);
121-
});
142+
}else{
143+
for(constlogofdata.parsedMessage){
144+
writeEmitter.fire(log.output+"\r\n");
145+
}
146+
}
147+
});
122148

123-
socket.addEventListener("close",()=>resolve());
149+
socket.addEventListener("error",(error)=>{
150+
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
151+
writeEmitter.fire(
152+
`Error watching agent logs on${baseUrlRaw}:${errToStr(error,"no further details")}\r\n`,
153+
);
124154
});
125155

126-
writeEmitter.fire("Build complete\r\n");
127-
constupdatedWorkspace=awaitclient.getWorkspace(workspace.id);
128-
writeEmitter.fire(
129-
`Workspace is now${updatedWorkspace.latest_build.status}\r\n`,
130-
);
131-
returnupdatedWorkspace;
156+
returnsocket;
132157
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp