@@ -25,6 +25,7 @@ import {
2525 isJsonRpcRequest ,
2626 isJsonRpcResponse ,
2727} from '@metamask/utils' ;
28+ import type { Json } from '@metamask/utils' ;
2829import type { PlatformFactory } from '@ocap/kernel-platforms' ;
2930
3031import { loadBundle } from './bundle-loader.ts' ;
@@ -37,6 +38,7 @@ import type {
3738 GCTools ,
3839} from '../liveslots/types.ts' ;
3940import { vatSyscallMethodSpecs , vatHandlers } from '../rpc/index.ts' ;
41+ import type { EvaluateResult } from '../rpc/index.ts' ;
4042import { makeVatKVStore } from '../store/vat-kv-store.ts' ;
4143import type { VatConfig , VatDeliveryResult , VatId } from '../types.ts' ;
4244import { isVatConfig , coerceVatSyscallObject } from '../types.ts' ;
@@ -105,6 +107,9 @@ export class VatSupervisor {
105107 /** Options to pass to the makePlatform function. */
106108 readonly #platformOptions: Record < string , unknown > ;
107109
110+ /** Compartment for REPL evaluation with vat exports in scope. */
111+ #evalCompartment: { evaluate : ( code : string ) => unknown } | null = null ;
112+
108113 /**
109114 * Construct a new VatSupervisor instance.
110115 *
@@ -151,6 +156,7 @@ export class VatSupervisor {
151156 this . #rpcServer = new RpcService ( vatHandlers , {
152157 initVat : this . #initVat. bind ( this ) ,
153158 handleDelivery : this . #deliver. bind ( this ) ,
159+ handleEvaluate : this . #evaluate. bind ( this ) ,
154160 } ) ;
155161
156162 Promise . all ( [
@@ -250,6 +256,81 @@ export class VatSupervisor {
250256 return [ this . #vatKVStore! . checkpoint ( ) , deliveryError ] ;
251257 }
252258
259+ /**
260+ * Evaluate code in the vat's REPL compartment.
261+ *
262+ * @param code - The code to evaluate.
263+ * @returns The result of the evaluation.
264+ */
265+ #evaluate( code : string ) : EvaluateResult {
266+ if ( ! this . #evalCompartment) {
267+ return { success : false , error : 'Vat not initialized' } ;
268+ }
269+ try {
270+ const result = this . #evalCompartment. evaluate ( code ) ;
271+ const serialized = this . #serializeResult( result ) ;
272+ if ( serialized === undefined ) {
273+ return { success : true } ;
274+ }
275+ return { success : true , value : serialized } ;
276+ } catch ( error ) {
277+ return {
278+ success : false ,
279+ error : error instanceof Error ? error . message : String ( error ) ,
280+ } ;
281+ }
282+ }
283+
284+ /**
285+ * Serialize a value for JSON-RPC transport.
286+ * Non-serializable values (functions, symbols, etc.) are converted to string representation.
287+ *
288+ * @param value - The value to serialize.
289+ * @returns A JSON-serializable value.
290+ */
291+ #serializeResult( value : unknown ) : Json | undefined {
292+ if ( value === undefined ) {
293+ return undefined ;
294+ }
295+ if ( value === null ) {
296+ return null ;
297+ }
298+ const type = typeof value ;
299+ if ( type === 'string' ) {
300+ return value as string ;
301+ }
302+ if ( type === 'number' ) {
303+ return value as number ;
304+ }
305+ if ( type === 'boolean' ) {
306+ return value as boolean ;
307+ }
308+ if ( type === 'bigint' ) {
309+ return `${ String ( value as bigint ) } n` ;
310+ }
311+ if ( type === 'symbol' ) {
312+ return ( value as symbol ) . toString ( ) ;
313+ }
314+ if ( type === 'function' ) {
315+ return `[Function: ${ ( value as { name ?: string } ) . name ?? 'anonymous' } ]` ;
316+ }
317+ if ( Array . isArray ( value ) ) {
318+ return value . map ( ( item ) => this . #serializeResult( item ) ?? null ) as Json [ ] ;
319+ }
320+ if ( type === 'object' ) {
321+ // Handle objects - try to serialize their properties
322+ const result : Record < string , Json > = { } ;
323+ for ( const key of Object . keys ( value as object ) ) {
324+ result [ key ] =
325+ this . #serializeResult( ( value as Record < string , unknown > ) [ key ] ) ??
326+ null ;
327+ }
328+ return result ;
329+ }
330+ // Fallback for any other types - unlikely to reach here
331+ return `[${ type } ]` ;
332+ }
333+
253334 /**
254335 * Initialize the vat by loading its user code bundle and creating a liveslots
255336 * instance to manage it.
@@ -357,6 +438,14 @@ export class VatSupervisor {
357438 endowments,
358439 inescapableGlobalProperties,
359440 } ) ;
441+
442+ // Create a separate compartment for REPL evaluation.
443+ // Vat exports become endowments, so they're directly in scope.
444+ this . #evalCompartment = new Compartment ( {
445+ harden : globalThis . harden ,
446+ ...vatNS ,
447+ } ) ;
448+
360449 return vatNS ;
361450 } ;
362451
0 commit comments