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

Commitcb2a4ec

Browse files
authored
Add agent metadata to status bar (#555)
1 parentcc07eb3 commitcb2a4ec

File tree

4 files changed

+155
-69
lines changed

4 files changed

+155
-69
lines changed

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
depending on how you connected, it could be possible to get two
1313
different sessions for an agent. Existing connections may still
1414
have this problem, only new connections are fixed.
15+
- Added an agent metadata monitor status bar item, so you can view your active
16+
agent metadata at a glance.
1517

1618
##[v1.9.2](https://github.com/coder/vscode-coder/releases/tag/v1.9.2) 2025-06-25
1719

‎src/agentMetadataHelper.ts‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import{Api}from"coder/site/src/api/api";
2+
import{WorkspaceAgent}from"coder/site/src/api/typesGenerated";
3+
import{EventSource}from"eventsource";
4+
import*asvscodefrom"vscode";
5+
import{createStreamingFetchAdapter}from"./api";
6+
import{
7+
AgentMetadataEvent,
8+
AgentMetadataEventSchemaArray,
9+
errToStr,
10+
}from"./api-helper";
11+
12+
exporttypeAgentMetadataWatcher={
13+
onChange:vscode.EventEmitter<null>["event"];
14+
dispose:()=>void;
15+
metadata?:AgentMetadataEvent[];
16+
error?:unknown;
17+
};
18+
19+
/**
20+
* Opens an SSE connection to watch metadata for a given workspace agent.
21+
* Emits onChange when metadata updates or an error occurs.
22+
*/
23+
exportfunctioncreateAgentMetadataWatcher(
24+
agentId:WorkspaceAgent["id"],
25+
restClient:Api,
26+
):AgentMetadataWatcher{
27+
// TODO: Is there a better way to grab the url and token?
28+
consturl=restClient.getAxiosInstance().defaults.baseURL;
29+
constmetadataUrl=newURL(
30+
`${url}/api/v2/workspaceagents/${agentId}/watch-metadata`,
31+
);
32+
consteventSource=newEventSource(metadataUrl.toString(),{
33+
fetch:createStreamingFetchAdapter(restClient.getAxiosInstance()),
34+
});
35+
36+
letdisposed=false;
37+
constonChange=newvscode.EventEmitter<null>();
38+
constwatcher:AgentMetadataWatcher={
39+
onChange:onChange.event,
40+
dispose:()=>{
41+
if(!disposed){
42+
eventSource.close();
43+
disposed=true;
44+
}
45+
},
46+
};
47+
48+
eventSource.addEventListener("data",(event)=>{
49+
try{
50+
constdataEvent=JSON.parse(event.data);
51+
constmetadata=AgentMetadataEventSchemaArray.parse(dataEvent);
52+
53+
// Overwrite metadata if it changed.
54+
if(JSON.stringify(watcher.metadata)!==JSON.stringify(metadata)){
55+
watcher.metadata=metadata;
56+
onChange.fire(null);
57+
}
58+
}catch(error){
59+
watcher.error=error;
60+
onChange.fire(null);
61+
}
62+
});
63+
64+
returnwatcher;
65+
}
66+
67+
exportfunctionformatMetadataError(error:unknown):string{
68+
return"Failed to query metadata: "+errToStr(error,"no error provided");
69+
}
70+
71+
exportfunctionformatEventLabel(metadataEvent:AgentMetadataEvent):string{
72+
returngetEventName(metadataEvent)+": "+getEventValue(metadataEvent);
73+
}
74+
75+
exportfunctiongetEventName(metadataEvent:AgentMetadataEvent):string{
76+
returnmetadataEvent.description.display_name.trim();
77+
}
78+
79+
exportfunctiongetEventValue(metadataEvent:AgentMetadataEvent):string{
80+
returnmetadataEvent.result.value.replace(/\n/g,"").trim();
81+
}

‎src/remote.ts‎

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import{isAxiosError}from"axios";
22
import{Api}from"coder/site/src/api/api";
3-
import{Workspace}from"coder/site/src/api/typesGenerated";
3+
import{Workspace,WorkspaceAgent}from"coder/site/src/api/typesGenerated";
44
importfindfrom"find-process";
55
import*asfsfrom"fs/promises";
66
import*asjsoncfrom"jsonc-parser";
@@ -9,6 +9,12 @@ import * as path from "path";
99
importprettyBytesfrom"pretty-bytes";
1010
import*assemverfrom"semver";
1111
import*asvscodefrom"vscode";
12+
import{
13+
createAgentMetadataWatcher,
14+
getEventValue,
15+
formatEventLabel,
16+
formatMetadataError,
17+
}from"./agentMetadataHelper";
1218
import{
1319
createHttpAgent,
1420
makeCoderSdk,
@@ -624,6 +630,10 @@ export class Remote {
624630
}),
625631
);
626632

633+
disposables.push(
634+
...this.createAgentMetadataStatusBar(agent,workspaceRestClient),
635+
);
636+
627637
this.storage.output.info("Remote setup complete");
628638

629639
// Returning the URL and token allows the plugin to authenticate its own
@@ -966,6 +976,56 @@ export class Remote {
966976
returnloop();
967977
}
968978

979+
/**
980+
* Creates and manages a status bar item that displays metadata information for a given workspace agent.
981+
* The status bar item updates dynamically based on changes to the agent's metadata,
982+
* and hides itself if no metadata is available or an error occurs.
983+
*/
984+
privatecreateAgentMetadataStatusBar(
985+
agent:WorkspaceAgent,
986+
restClient:Api,
987+
):vscode.Disposable[]{
988+
conststatusBarItem=vscode.window.createStatusBarItem(
989+
"agentMetadata",
990+
vscode.StatusBarAlignment.Left,
991+
);
992+
993+
constagentWatcher=createAgentMetadataWatcher(agent.id,restClient);
994+
995+
constonChangeDisposable=agentWatcher.onChange(()=>{
996+
if(agentWatcher.error){
997+
consterrMessage=formatMetadataError(agentWatcher.error);
998+
this.storage.output.warn(errMessage);
999+
1000+
statusBarItem.text="$(warning) Agent Status Unavailable";
1001+
statusBarItem.tooltip=errMessage;
1002+
statusBarItem.color=newvscode.ThemeColor(
1003+
"statusBarItem.warningForeground",
1004+
);
1005+
statusBarItem.backgroundColor=newvscode.ThemeColor(
1006+
"statusBarItem.warningBackground",
1007+
);
1008+
statusBarItem.show();
1009+
return;
1010+
}
1011+
1012+
if(agentWatcher.metadata&&agentWatcher.metadata.length>0){
1013+
statusBarItem.text=
1014+
"$(dashboard) "+getEventValue(agentWatcher.metadata[0]);
1015+
statusBarItem.tooltip=agentWatcher.metadata
1016+
.map((metadata)=>formatEventLabel(metadata))
1017+
.join("\n");
1018+
statusBarItem.color=undefined;
1019+
statusBarItem.backgroundColor=undefined;
1020+
statusBarItem.show();
1021+
}else{
1022+
statusBarItem.hide();
1023+
}
1024+
});
1025+
1026+
return[statusBarItem,agentWatcher,onChangeDisposable];
1027+
}
1028+
9691029
// closeRemote ends the current remote session.
9701030
publicasynccloseRemote(){
9711031
awaitvscode.commands.executeCommand("workbench.action.remote.close");

‎src/workspacesProvider.ts‎

Lines changed: 11 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import {
44
WorkspaceAgent,
55
WorkspaceApp,
66
}from"coder/site/src/api/typesGenerated";
7-
import{EventSource}from"eventsource";
87
import*aspathfrom"path";
98
import*asvscodefrom"vscode";
10-
import{createStreamingFetchAdapter}from"./api";
9+
import{
10+
AgentMetadataWatcher,
11+
createAgentMetadataWatcher,
12+
formatEventLabel,
13+
formatMetadataError,
14+
}from"./agentMetadataHelper";
1115
import{
1216
AgentMetadataEvent,
13-
AgentMetadataEventSchemaArray,
1417
extractAllAgents,
1518
extractAgents,
16-
errToStr,
1719
}from"./api-helper";
1820
import{Storage}from"./storage";
1921

@@ -22,13 +24,6 @@ export enum WorkspaceQuery {
2224
All="",
2325
}
2426

25-
typeAgentWatcher={
26-
onChange:vscode.EventEmitter<null>["event"];
27-
dispose:()=>void;
28-
metadata?:AgentMetadataEvent[];
29-
error?:unknown;
30-
};
31-
3227
/**
3328
* Polls workspaces using the provided REST client and renders them in a tree.
3429
*
@@ -42,7 +37,8 @@ export class WorkspaceProvider
4237
{
4338
// Undefined if we have never fetched workspaces before.
4439
privateworkspaces:WorkspaceTreeItem[]|undefined;
45-
privateagentWatchers:Record<WorkspaceAgent["id"],AgentWatcher>={};
40+
privateagentWatchers:Record<WorkspaceAgent["id"],AgentMetadataWatcher>=
41+
{};
4642
privatetimeout:NodeJS.Timeout|undefined;
4743
privatefetching=false;
4844
privatevisible=false;
@@ -139,7 +135,7 @@ export class WorkspaceProvider
139135
returnthis.agentWatchers[agent.id];
140136
}
141137
// Otherwise create a new watcher.
142-
constwatcher=monitorMetadata(agent.id,restClient);
138+
constwatcher=createAgentMetadataWatcher(agent.id,restClient);
143139
watcher.onChange(()=>this.refresh());
144140
this.agentWatchers[agent.id]=watcher;
145141
returnwatcher;
@@ -313,53 +309,6 @@ export class WorkspaceProvider
313309
}
314310
}
315311

316-
// monitorMetadata opens an SSE endpoint to monitor metadata on the specified
317-
// agent and registers a watcher that can be disposed to stop the watch and
318-
// emits an event when the metadata changes.
319-
functionmonitorMetadata(
320-
agentId:WorkspaceAgent["id"],
321-
restClient:Api,
322-
):AgentWatcher{
323-
// TODO: Is there a better way to grab the url and token?
324-
consturl=restClient.getAxiosInstance().defaults.baseURL;
325-
constmetadataUrl=newURL(
326-
`${url}/api/v2/workspaceagents/${agentId}/watch-metadata`,
327-
);
328-
consteventSource=newEventSource(metadataUrl.toString(),{
329-
fetch:createStreamingFetchAdapter(restClient.getAxiosInstance()),
330-
});
331-
332-
letdisposed=false;
333-
constonChange=newvscode.EventEmitter<null>();
334-
constwatcher:AgentWatcher={
335-
onChange:onChange.event,
336-
dispose:()=>{
337-
if(!disposed){
338-
eventSource.close();
339-
disposed=true;
340-
}
341-
},
342-
};
343-
344-
eventSource.addEventListener("data",(event)=>{
345-
try{
346-
constdataEvent=JSON.parse(event.data);
347-
constmetadata=AgentMetadataEventSchemaArray.parse(dataEvent);
348-
349-
// Overwrite metadata if it changed.
350-
if(JSON.stringify(watcher.metadata)!==JSON.stringify(metadata)){
351-
watcher.metadata=metadata;
352-
onChange.fire(null);
353-
}
354-
}catch(error){
355-
watcher.error=error;
356-
onChange.fire(null);
357-
}
358-
});
359-
360-
returnwatcher;
361-
}
362-
363312
/**
364313
* A tree item that represents a collapsible section with child items
365314
*/
@@ -375,20 +324,14 @@ class SectionTreeItem extends vscode.TreeItem {
375324

376325
classErrorTreeItemextendsvscode.TreeItem{
377326
constructor(error:unknown){
378-
super(
379-
"Failed to query metadata: "+errToStr(error,"no error provided"),
380-
vscode.TreeItemCollapsibleState.None,
381-
);
327+
super(formatMetadataError(error),vscode.TreeItemCollapsibleState.None);
382328
this.contextValue="coderAgentMetadata";
383329
}
384330
}
385331

386332
classAgentMetadataTreeItemextendsvscode.TreeItem{
387333
constructor(metadataEvent:AgentMetadataEvent){
388-
constlabel=
389-
metadataEvent.description.display_name.trim()+
390-
": "+
391-
metadataEvent.result.value.replace(/\n/g,"").trim();
334+
constlabel=formatEventLabel(metadataEvent);
392335

393336
super(label,vscode.TreeItemCollapsibleState.None);
394337
constcollected_at=newDate(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp