@@ -438,6 +438,118 @@ it.live("retries a failed selection restart session open before completing", ()
438438 ) ,
439439) ;
440440
441+ it . live ( "Stop during a restart interrupts the turn the restart could not stop" , ( ) =>
442+ Effect . scoped (
443+ Effect . gen ( function * ( ) {
444+ const cwd = yield * checkpointWorkspace ( "selection-restart-stop" ) ;
445+ const threadId = ThreadId . make ( "thread:selection-restart-stop" ) ;
446+ const state = yield * Ref . make < RestartAdapterState > ( {
447+ activeTurn : null ,
448+ opened : [ ] ,
449+ started : [ ] ,
450+ closedSessionCount : 0 ,
451+ failedReplacementOpen : false ,
452+ } ) ;
453+ const base = makeRestartAdapter ( state ) ;
454+ // A wedged provider ignores the restart's soft cancel; only Stop ends its turn.
455+ const adapter : ProviderAdapter . ProviderAdapterV2 [ "Service" ] = {
456+ ...base ,
457+ openSession : ( input ) =>
458+ base . openSession ( input ) . pipe (
459+ Effect . map ( ( session ) => ( {
460+ ...session ,
461+ interruptTurn : ( interrupt : ProviderAdapter . ProviderAdapterV2InterruptInput ) =>
462+ interrupt . requestRuntimeRestart === true
463+ ? session . interruptTurn ( interrupt )
464+ : Effect . never ,
465+ } ) ) ,
466+ ) ,
467+ } ;
468+
469+ const result = yield * Effect . gen ( function * ( ) {
470+ const orchestrator = yield * Orchestrator . OrchestratorV2 ;
471+ const worker = yield * EffectWorker . OrchestrationEffectWorkerV2 ;
472+ const running = yield * orchestrator . streamDomainEvents . pipe (
473+ Stream . filter (
474+ ( event ) => event . type === "provider-turn.updated" && event . payload . status === "running" ,
475+ ) ,
476+ Stream . take ( 1 ) ,
477+ Stream . runDrain ,
478+ Effect . forkScoped ( { startImmediately : true } ) ,
479+ ) ;
480+ yield * orchestrator . dispatch ( {
481+ type : "thread.create" ,
482+ createdBy : "user" ,
483+ creationSource : "web" ,
484+ commandId : CommandId . make ( "command:selection-restart-stop:create" ) ,
485+ threadId,
486+ projectId : ProjectId . make ( "project:selection-restart-stop" ) ,
487+ title : "Stop during restart" ,
488+ modelSelection : initialSelection ,
489+ runtimeMode : "full-access" ,
490+ interactionMode : "default" ,
491+ branch : null ,
492+ worktreePath : cwd ,
493+ } ) ;
494+ yield * orchestrator . dispatch ( {
495+ type : "message.dispatch" ,
496+ createdBy : "user" ,
497+ creationSource : "web" ,
498+ commandId : CommandId . make ( "command:selection-restart-stop:first" ) ,
499+ threadId,
500+ messageId : MessageId . make ( "message:selection-restart-stop:first" ) ,
501+ text : "first" ,
502+ attachments : [ ] ,
503+ modelSelection : initialSelection ,
504+ dispatchMode : { type : "start_immediately" } ,
505+ } ) ;
506+ yield * worker . drain ( ) ;
507+ yield * Fiber . join ( running ) ;
508+ const runId = ( yield * orchestrator . getThreadProjection ( threadId ) ) . runs [ 0 ] ! . id ;
509+
510+ yield * orchestrator . dispatch ( {
511+ type : "message.dispatch" ,
512+ createdBy : "user" ,
513+ creationSource : "web" ,
514+ commandId : CommandId . make ( "command:selection-restart-stop:steer" ) ,
515+ threadId,
516+ messageId : MessageId . make ( "message:selection-restart-stop:steer" ) ,
517+ text : "Are you still going?" ,
518+ attachments : [ ] ,
519+ modelSelection : initialSelection ,
520+ dispatchMode : { type : "restart_active" , targetRunId : runId } ,
521+ } ) ;
522+ yield * orchestrator . dispatch ( {
523+ type : "run.interrupt" ,
524+ commandId : CommandId . make ( "command:selection-restart-stop:stop" ) ,
525+ threadId,
526+ runId,
527+ } ) ;
528+ yield * worker . drain ( ) ;
529+ return {
530+ projection : yield * orchestrator . getThreadProjection ( threadId ) ,
531+ captured : yield * Ref . get ( state ) ,
532+ } ;
533+ } ) . pipe (
534+ Effect . provide (
535+ ProviderReplayHarness . layerWithRegistry (
536+ { name : "selection-restart-stop" } ,
537+ ProviderAdapterRegistry . layerSingle ( adapter ) ,
538+ ) ,
539+ ) ,
540+ ) ;
541+
542+ assert . isNull ( result . captured . activeTurn ) ;
543+ assert . lengthOf ( result . captured . started , 1 ) ;
544+ assert . equal ( result . projection . runs [ 0 ] ?. status , "interrupted" ) ;
545+ assert . deepEqual (
546+ result . projection . providerTurns . map ( ( turn ) => turn . status ) ,
547+ [ "interrupted" ] ,
548+ ) ;
549+ } ) ,
550+ ) ,
551+ ) ;
552+
441553it . live (
442554 "restarts the live session on a model change when a newer stopped session record exists" ,
443555 ( ) =>
0 commit comments