@@ -18,6 +18,7 @@ import { getPreferredElizaAgentWebUiUrl } from "@/lib/eliza-agent-web-ui";
1818import { adminService } from "@/lib/services/admin" ;
1919import { reusesExistingElizaCharacter } from "@/lib/services/eliza-agent-config" ;
2020import { elizaSandboxService } from "@/lib/services/eliza-sandbox" ;
21+ import { provisioningJobService } from "@/lib/services/provisioning-jobs" ;
2122import { getStewardAgent } from "@/lib/services/steward-client" ;
2223import type {
2324 AgentAdminDetailsDto ,
@@ -364,63 +365,63 @@ app.delete("/", async (c) => {
364365 return c . json ( { success : false , error : "Agent not found" } , 404 ) ;
365366 }
366367
367- if ( existing . node_id && existing . sandbox_id ) {
368- const forwarded = await deleteDockerBackedAgentViaControlPlane (
369- c ,
370- user ,
371- agentId ,
368+ if ( existing . status === "provisioning" ) {
369+ return c . json (
370+ { success : false , error : "Agent provisioning is in progress" } ,
371+ 409 ,
372372 ) ;
373- if ( forwarded ) return forwarded ;
374373 }
375374
376- const deleted = await elizaSandboxService . deleteAgent (
375+ // Async delete via the same job-queue path agent_provision uses. This
376+ // moves the SSH stop, Neon cleanup, and per-agent key revoke off the
377+ // request thread so a slow / unreachable Hetzner core can no longer
378+ // make the API hang or silently return 200 while the container lives
379+ // on. Idempotent: a second DELETE while a job is in flight reuses
380+ // the existing one.
381+ const enqueueResult = await provisioningJobService . enqueueAgentDeleteOnce ( {
377382 agentId,
378- user . organization_id ,
379- ) ;
380- if ( ! deleted . success ) {
381- const status =
382- deleted . error === "Agent not found"
383- ? 404
384- : deleted . error === "Agent provisioning is in progress"
385- ? 409
386- : 500 ;
387- return c . json ( { success : false , error : deleted . error } , status ) ;
388- }
389-
390- const characterId = deleted . deletedSandbox . character_id ;
391- const reusesExistingCharacter = reusesExistingElizaCharacter (
392- deleted . deletedSandbox . agent_config ,
393- ) ;
383+ organizationId : user . organization_id ,
384+ userId : user . id ,
385+ } ) ;
394386
395- if ( characterId && ! reusesExistingCharacter ) {
396- try {
397- await userCharactersRepository . delete ( characterId ) ;
398- logger . info ( "[agent-api] Cleaned up linked character after delete" , {
399- agentId,
400- characterId,
401- } ) ;
402- } catch ( characterErr ) {
403- logger . warn (
404- "[agent-api] Failed to clean up linked character after delete" ,
405- {
406- agentId,
407- characterId,
408- error :
409- characterErr instanceof Error
410- ? characterErr . message
411- : String ( characterErr ) ,
412- } ,
413- ) ;
414- }
415- }
387+ // Best-effort wake of the worker so the user does not wait for the
388+ // next cron tick. Same pattern as the provision path.
389+ void provisioningJobService . triggerImmediate ( c . env ) . catch ( ( ) => {
390+ // Logged inside the service; nothing actionable here.
391+ } ) ;
416392
417- logger . info ( "[agent-api] Agent deleted " , {
393+ logger . info ( "[agent-api] Agent delete enqueued " , {
418394 agentId,
419395 orgId : user . organization_id ,
396+ jobId : enqueueResult . job . id ,
397+ created : enqueueResult . created ,
420398 } ) ;
421399
422- return c . json ( { success : true } ) ;
400+ return c . json (
401+ {
402+ success : true ,
403+ created : enqueueResult . created ,
404+ alreadyInProgress : ! enqueueResult . created ,
405+ message : enqueueResult . created
406+ ? "Delete job created. Poll the job endpoint for status."
407+ : "Delete is already in progress." ,
408+ data : {
409+ jobId : enqueueResult . job . id ,
410+ agentId,
411+ status : enqueueResult . job . status ,
412+ } ,
413+ polling : {
414+ endpoint : `/api/v1/jobs/${ enqueueResult . job . id } ` ,
415+ intervalMs : 5_000 ,
416+ expectedDurationMs : 30_000 ,
417+ } ,
418+ } ,
419+ 202 ,
420+ ) ;
423421 } catch ( error ) {
422+ if ( error instanceof Error && error . message === "Agent not found" ) {
423+ return c . json ( { success : false , error : "Agent not found" } , 404 ) ;
424+ }
424425 logger . error ( "[agent-api] DELETE /agents/:agentId error" , { error } ) ;
425426 return failureResponse ( c , error ) ;
426427 }
0 commit comments