@@ -49,6 +49,10 @@ export function getSandbox(
4949 stub . setSleepAfter ( options . sleepAfter ) ;
5050 }
5151
52+ if ( options ?. keepAlive !== undefined ) {
53+ stub . setKeepAlive ( options . keepAlive ) ;
54+ }
55+
5256 return stub ;
5357}
5458
@@ -64,6 +68,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
6468 private defaultSession : string | null = null ;
6569 envVars : Record < string , string > = { } ;
6670 private logger : ReturnType < typeof createLogger > ;
71+ private keepAliveEnabled : boolean = false ;
6772
6873 constructor ( ctx : DurableObject [ 'ctx' ] , env : Env ) {
6974 super ( ctx , env ) ;
@@ -131,6 +136,16 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
131136 this . sleepAfter = sleepAfter ;
132137 }
133138
139+ // RPC method to enable keepAlive mode
140+ async setKeepAlive ( keepAlive : boolean ) : Promise < void > {
141+ this . keepAliveEnabled = keepAlive ;
142+ if ( keepAlive ) {
143+ this . logger . info ( 'KeepAlive mode enabled - container will stay alive until explicitly destroyed' ) ;
144+ } else {
145+ this . logger . info ( 'KeepAlive mode disabled - container will timeout normally' ) ;
146+ }
147+ }
148+
134149 // RPC method to set environment variables
135150 async setEnvVars ( envVars : Record < string , string > ) : Promise < void > {
136151 // Update local state for new sessions
@@ -220,6 +235,22 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
220235 this . logger . error ( 'Sandbox error' , error instanceof Error ? error : new Error ( String ( error ) ) ) ;
221236 }
222237
238+ /**
239+ * Override onActivityExpired to prevent automatic shutdown when keepAlive is enabled
240+ * When keepAlive is disabled, calls parent implementation which stops the container
241+ */
242+ override async onActivityExpired ( ) : Promise < void > {
243+ if ( this . keepAliveEnabled ) {
244+ this . logger . debug ( 'Activity expired but keepAlive is enabled - container will stay alive' ) ;
245+ // Do nothing - don't call stop(), container stays alive
246+ } else {
247+ // Default behavior: stop the container
248+ this . logger . debug ( 'Activity expired - stopping container' ) ;
249+ await super . onActivityExpired ( ) ;
250+ }
251+ }
252+
253+
223254 // Override fetch to route internal container requests to appropriate ports
224255 override async fetch ( request : Request ) : Promise < Response > {
225256 // Extract or generate trace ID from request
@@ -327,7 +358,6 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
327358 const startTime = Date . now ( ) ;
328359 const timestamp = new Date ( ) . toISOString ( ) ;
329360
330- // Handle timeout
331361 let timeoutId : NodeJS . Timeout | undefined ;
332362
333363 try {
@@ -592,8 +622,7 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
592622 } ;
593623 }
594624
595-
596- // Streaming methods - return ReadableStream for RPC compatibility
625+ // Streaming methods - return ReadableStream for RPC compatibility
597626 async execStream ( command : string , options ?: StreamOptions ) : Promise < ReadableStream < Uint8Array > > {
598627 // Check for cancellation
599628 if ( options ?. signal ?. aborted ) {
@@ -617,6 +646,9 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
617646 return this . client . commands . executeStream ( command , sessionId ) ;
618647 }
619648
649+ /**
650+ * Stream logs from a background process as a ReadableStream.
651+ */
620652 async streamProcessLogs ( processId : string , options ?: { signal ?: AbortSignal } ) : Promise < ReadableStream < Uint8Array > > {
621653 // Check for cancellation
622654 if ( options ?. signal ?. aborted ) {
0 commit comments