@@ -19,6 +19,8 @@ import {
1919 isTerminalStatus ,
2020 type SendCommandInput ,
2121 type SendCommandOutput ,
22+ type StopInput ,
23+ type StopOutput ,
2224 type TaskRunStatus ,
2325 type WatchInput ,
2426} from "./schemas" ;
@@ -520,6 +522,69 @@ export class CloudTaskService extends TypedEventEmitter<CloudTaskEvents> {
520522 }
521523 }
522524
525+ /**
526+ * Stop a cloud run for good: the backend interrupts the agent, snapshots the
527+ * session for later resume, tears down the sandbox, and marks the run
528+ * cancelled. Distinct from sendCommand("cancel"), which only interrupts the
529+ * in-flight turn and leaves the sandbox running.
530+ */
531+ async stop ( input : StopInput ) : Promise < StopOutput > {
532+ const url = `${ input . apiHost } /api/projects/${ input . teamId } /tasks/${ input . taskId } /runs/${ input . runId } /cancel/` ;
533+
534+ try {
535+ const response = await this . auth . authenticatedFetch ( url , {
536+ method : "POST" ,
537+ headers : {
538+ "Content-Type" : "application/json" ,
539+ } ,
540+ body : JSON . stringify ( input . reason ? { reason : input . reason } : { } ) ,
541+ } ) ;
542+
543+ if ( ! response . ok ) {
544+ const errorText = await response . text ( ) . catch ( ( ) => "" ) ;
545+ let errorMessage = `Stop failed with status ${ response . status } ` ;
546+ try {
547+ const errorJson = JSON . parse ( errorText ) as { error ?: unknown } ;
548+ if ( typeof errorJson . error === "string" && errorJson . error ) {
549+ errorMessage = errorJson . error ;
550+ }
551+ } catch {
552+ if ( errorText ) errorMessage = errorText ;
553+ }
554+
555+ this . log . warn ( "Cloud run stop failed" , {
556+ taskId : input . taskId ,
557+ runId : input . runId ,
558+ status : response . status ,
559+ error : errorMessage ,
560+ } ) ;
561+ return {
562+ success : false ,
563+ error : errorMessage ,
564+ // 503 = Temporal briefly unreachable on the backend; 5xx = transient server trouble.
565+ retryable : response . status === 503 || response . status >= 500 ,
566+ } ;
567+ }
568+
569+ const data = ( await response . json ( ) ) as { status ?: string } ;
570+ this . log . info ( "Cloud run stop accepted" , {
571+ taskId : input . taskId ,
572+ runId : input . runId ,
573+ runStatus : data . status ,
574+ } ) ;
575+ return { success : true , runStatus : data . status } ;
576+ } catch ( error ) {
577+ const errorMessage =
578+ error instanceof Error ? error . message : "Unknown error" ;
579+ this . log . error ( "Cloud run stop error" , {
580+ taskId : input . taskId ,
581+ runId : input . runId ,
582+ error : errorMessage ,
583+ } ) ;
584+ return { success : false , error : errorMessage , retryable : true } ;
585+ }
586+ }
587+
523588 @preDestroy ( )
524589 unwatchAll ( ) : void {
525590 for ( const key of [ ...this . watchers . keys ( ) ] ) {
0 commit comments