@@ -105,6 +105,35 @@ describe('OneComputerSandboxRuntimeAdapter', () => {
105105 expect ( store . verifyChain ( task . id ) ) . toBe ( true )
106106 } )
107107
108+ it ( 'deletes the known provider sandbox when cancellation occurs during provisioning' , async ( ) => {
109+ const root = await mkdtemp ( path . join ( tmpdir ( ) , 'onevibe-onecomputer-cancel-provisioning-' ) )
110+ roots . push ( root )
111+ const { TaskStore } = await import ( './store.js' )
112+ const { OneComputerSandboxRuntimeAdapter } = await import ( './onecomputer-sandbox-runner.js' )
113+ const store = new TaskStore ( root )
114+ await store . initialize ( )
115+ const task = await store . createTask ( 'Cancel while a sandbox provisions' , 'onecomputer' )
116+ const client = {
117+ createSandbox : vi . fn ( async ( ) => ( { id : 'sandbox-provisioning' , state : 'provisioning' , provider : 'kasm-local' } ) ) ,
118+ getSandbox : vi . fn ( async ( ) => ( { id : 'sandbox-provisioning' , state : 'provisioning' , provider : 'kasm-local' } ) ) ,
119+ deleteSandbox : vi . fn ( async ( ) => undefined ) ,
120+ exec : vi . fn ( ) ,
121+ startVisualRuntime : vi . fn ( ) ,
122+ getVisualScreenshot : vi . fn ( ) ,
123+ } as unknown as OneComputerClient
124+ const adapter = new OneComputerSandboxRuntimeAdapter ( client , { gatewayEnforced : true , retainSandbox : false , visualRuntime : false , pollMilliseconds : 10_000 } )
125+ const controller = new AbortController ( )
126+ const run = adapter . run ( { task, store, signal : controller . signal , prompt : task . prompt , continuation : false , requestUserInput : async ( ) => 'unused' } )
127+
128+ await vi . waitFor ( ( ) => expect ( store . getTask ( task . id ) . securityContext ) . toMatchObject ( { sandboxId : 'sandbox-provisioning' , sandboxState : 'provisioning' , executionBoundary : 'onecomputer_sandbox' } ) )
129+ controller . abort ( )
130+
131+ await expect ( run ) . rejects . toMatchObject ( { name : 'AbortError' } )
132+ expect ( client . deleteSandbox ) . toHaveBeenCalledWith ( 'sandbox-provisioning' )
133+ expect ( store . getTask ( task . id ) . securityContext ) . toMatchObject ( { sandboxId : 'sandbox-provisioning' , sandboxState : 'destroyed' } )
134+ expect ( store . listEvents ( task . id ) . some ( ( event ) => event . label === 'Ephemeral sandbox destroyed' ) ) . toBe ( true )
135+ } )
136+
108137 it ( 'reuses an explicitly retained sandbox for a continuation' , async ( ) => {
109138 const root = await mkdtemp ( path . join ( tmpdir ( ) , 'onevibe-onecomputer-retained-' ) )
110139 roots . push ( root )
0 commit comments