11import { homedir } from "node:os" ;
22import { join } from "node:path" ;
3+ import { cloudApiClient } from "main/lib/cloud-api-client" ;
34import { db } from "main/lib/db" ;
45import { nanoid } from "nanoid" ;
56import { SUPERSET_DIR_NAME , WORKTREES_DIR_NAME } from "shared/constants" ;
@@ -114,11 +115,17 @@ export const createWorkspacesRouter = () => {
114115 return db . data . workspaces . slice ( ) . sort ( ( a , b ) => a . tabOrder - b . tabOrder ) ;
115116 } ) ,
116117
117- getAllGrouped : publicProcedure . query ( ( ) => {
118+ getAllGrouped : publicProcedure . query ( async ( ) => {
118119 const activeProjects = db . data . projects . filter (
119120 ( p ) => p . tabOrder !== null ,
120121 ) ;
121122
123+ // Fetch live sandbox statuses
124+ const sandboxResult = await cloudApiClient . listSandboxes ( ) ;
125+ const liveSandboxes = new Map (
126+ ( sandboxResult . sandboxes ?? [ ] ) . map ( ( s ) => [ s . id , s . status ] ) ,
127+ ) ;
128+
122129 const groupsMap = new Map <
123130 string ,
124131 {
@@ -138,6 +145,8 @@ export const createWorkspacesRouter = () => {
138145 createdAt : number ;
139146 updatedAt : number ;
140147 lastOpenedAt : number ;
148+ cloudSandboxId ?: string ;
149+ cloudSandboxStatus ?: string ;
141150 } > ;
142151 }
143152 > ( ) ;
@@ -161,9 +170,24 @@ export const createWorkspacesRouter = () => {
161170
162171 for ( const workspace of workspaces ) {
163172 if ( groupsMap . has ( workspace . projectId ) ) {
173+ const worktree = db . data . worktrees . find (
174+ ( wt ) => wt . id === workspace . worktreeId ,
175+ ) ;
176+ const sandboxId = worktree ?. cloudSandbox ?. id ;
177+ // Use live status if available, fallback to stored status
178+ const liveStatus = sandboxId
179+ ? liveSandboxes . get ( sandboxId )
180+ : undefined ;
181+ // If sandbox exists in db but not in live list, it's stopped/deleted
182+ const status = sandboxId
183+ ? ( liveStatus ?? "stopped" )
184+ : worktree ?. cloudSandbox ?. status ;
185+
164186 groupsMap . get ( workspace . projectId ) ?. workspaces . push ( {
165187 ...workspace ,
166188 worktreePath : getWorktreePath ( workspace . worktreeId ) ?? "" ,
189+ cloudSandboxId : sandboxId ,
190+ cloudSandboxStatus : status ,
167191 } ) ;
168192 }
169193 }
@@ -295,6 +319,19 @@ export const createWorkspacesRouter = () => {
295319 ( p ) => p . id === workspace . projectId ,
296320 ) ;
297321
322+ // Kill cloud sandbox if present
323+ if ( worktree ?. cloudSandbox ?. id ) {
324+ try {
325+ console . log (
326+ `Deleting cloud sandbox ${ worktree . cloudSandbox . id } for worktree ${ worktree . id } ` ,
327+ ) ;
328+ await cloudApiClient . deleteSandbox ( worktree . cloudSandbox . id ) ;
329+ } catch ( error ) {
330+ console . error ( "Failed to delete cloud sandbox:" , error ) ;
331+ // Continue with deletion even if sandbox deletion fails
332+ }
333+ }
334+
298335 if ( worktree && project ) {
299336 try {
300337 const exists = await worktreeExists (
@@ -408,6 +445,26 @@ export const createWorkspacesRouter = () => {
408445
409446 return { success : true } ;
410447 } ) ,
448+
449+ getDanglingSandboxes : publicProcedure . query ( async ( ) => {
450+ // Get all sandboxes from the cloud API
451+ const result = await cloudApiClient . listSandboxes ( ) ;
452+ if ( ! result . success || ! result . sandboxes ) {
453+ return [ ] ;
454+ }
455+
456+ // Get all sandbox IDs that are linked to worktrees
457+ const linkedSandboxIds = new Set (
458+ db . data . worktrees
459+ . filter ( ( wt ) => wt . cloudSandbox ?. id )
460+ . map ( ( wt ) => wt . cloudSandbox ?. id ) ,
461+ ) ;
462+
463+ // Return only running sandboxes that are not linked to any worktree
464+ return result . sandboxes . filter (
465+ ( s ) => ! linkedSandboxIds . has ( s . id ) && s . status === "running" ,
466+ ) ;
467+ } ) ,
411468 } ) ;
412469} ;
413470
0 commit comments