1
1
import { API } from "api/api" ;
2
2
import { getErrorDetail , getErrorMessage } from "api/errors" ;
3
3
import { template as templateQueryOptions } from "api/queries/templates" ;
4
- import type { Workspace , WorkspaceStatus } from "api/typesGenerated" ;
4
+ import type {
5
+ Workspace ,
6
+ WorkspaceApp ,
7
+ WorkspaceStatus ,
8
+ } from "api/typesGenerated" ;
5
9
import { Button } from "components/Button/Button" ;
6
10
import { Loader } from "components/Loader/Loader" ;
7
11
import { Margins } from "components/Margins/Margins" ;
@@ -105,6 +109,8 @@ const TaskPage = () => {
105
109
"stopping" ,
106
110
] ;
107
111
112
+ const [ sidebarApp , sidebarAppStatus ] = getSidebarApp ( task ) ;
113
+
108
114
if ( waitingStatuses . includes ( task . workspace . latest_build . status ) ) {
109
115
// If no template yet, use an indeterminate progress bar.
110
116
const transition = ( template &&
@@ -171,7 +177,7 @@ const TaskPage = () => {
171
177
</ Margins >
172
178
) ;
173
179
} else {
174
- content = < TaskApps task = { task } /> ;
180
+ content = < TaskApps task = { task } sidebarApp = { sidebarApp } /> ;
175
181
}
176
182
177
183
return (
@@ -181,7 +187,11 @@ const TaskPage = () => {
181
187
</ Helmet >
182
188
< PanelGroup autoSaveId = "task" direction = "horizontal" >
183
189
< Panel defaultSize = { 25 } minSize = { 20 } >
184
- < TaskSidebar task = { task } />
190
+ < TaskSidebar
191
+ task = { task }
192
+ sidebarApp = { sidebarApp }
193
+ sidebarAppStatus = { sidebarAppStatus }
194
+ />
185
195
</ Panel >
186
196
< PanelResizeHandle >
187
197
< div className = "w-1 bg-border h-full hover:bg-border-hover transition-all relative" />
@@ -229,3 +239,66 @@ export const data = {
229
239
} satisfies Task ;
230
240
} ,
231
241
} ;
242
+
243
+ const getSidebarApp = (
244
+ task : Task ,
245
+ ) : [ WorkspaceApp | null , "error" | "loading" | "healthy" ] => {
246
+ if ( ! task . workspace . latest_build . job . completed_at ) {
247
+ // while the workspace build is running, we don't have a sidebar app yet
248
+ return [ null , "loading" ] ;
249
+ }
250
+
251
+ // Ensure all the agents are healthy before continuing.
252
+ const healthyAgents = task . workspace . latest_build . resources
253
+ . flatMap ( ( res ) => res . agents )
254
+ . filter ( ( agt ) => ! ! agt && agt . health . healthy ) ;
255
+ if ( ! healthyAgents ) {
256
+ return [ null , "loading" ] ;
257
+ }
258
+
259
+ // TODO(Cian): Improve logic for determining sidebar app.
260
+ // For now, we take the first workspace_app with at least one app_status.
261
+ const sidebarApps = healthyAgents
262
+ . flatMap ( ( a ) => a ?. apps )
263
+ . filter ( ( a ) => ! ! a && a . statuses && a . statuses . length > 0 ) ;
264
+
265
+ // At this point the workspace build is complete but no app has reported a status
266
+ // indicating that it is ready. Most well-behaved agentic AI applications will
267
+ // indicate their readiness status via MCP(coder_report_task).
268
+ // It's also possible that the application is just not ready yet.
269
+ // We return "loading" instead of "error" to avoid showing an error state if the app
270
+ // becomes available shortly after. The tradeoff is that users may see a loading state
271
+ // indefinitely if there's a genuine issue, but this is preferable to false error alerts.
272
+ if ( ! sidebarApps ) {
273
+ return [ null , "loading" ] ;
274
+ }
275
+
276
+ const sidebarApp = sidebarApps [ 0 ] ;
277
+ if ( ! sidebarApp ) {
278
+ return [ null , "loading" ] ;
279
+ }
280
+
281
+ // "disabled" means that the health check is disabled, so we assume
282
+ // that the app is healthy
283
+ if ( sidebarApp . health === "disabled" ) {
284
+ return [ sidebarApp , "healthy" ] ;
285
+ }
286
+ if ( sidebarApp . health === "healthy" ) {
287
+ return [ sidebarApp , "healthy" ] ;
288
+ }
289
+ if ( sidebarApp . health === "initializing" ) {
290
+ return [ sidebarApp , "loading" ] ;
291
+ }
292
+ if ( sidebarApp . health === "unhealthy" ) {
293
+ return [ sidebarApp , "error" ] ;
294
+ }
295
+
296
+ // exhaustiveness check
297
+ const _ : never = sidebarApp . health ;
298
+ // this should never happen
299
+ console . error (
300
+ "Task workspace has a finished build but the sidebar app is in an unknown health state" ,
301
+ task . workspace ,
302
+ ) ;
303
+ return [ null , "error" ] ;
304
+ } ;
0 commit comments