@@ -1036,53 +1036,83 @@ const makeWsRpcLayer = (
10361036 ORCHESTRATION_WS_METHODS . dispatchCommand ,
10371037 Effect . gen ( function * ( ) {
10381038 const normalizedCommand = yield * normalizeDispatchCommand ( command ) ;
1039- const shouldStopSessionAfterArchive =
1040- normalizedCommand . type === "thread.archive"
1041- ? yield * projectionSnapshotQuery
1042- . getThreadShellById ( normalizedCommand . threadId )
1043- . pipe (
1044- Effect . map (
1045- Option . match ( {
1046- onNone : ( ) => false ,
1047- onSome : ( thread ) =>
1048- thread . session !== null && thread . session . status !== "stopped" ,
1049- } ) ,
1050- ) ,
1051- Effect . orElseSucceed ( ( ) => false ) ,
1052- )
1053- : false ;
1039+ // Archive and settle both mean "done with this thread", so a
1040+ // live provider session must not keep running background work
1041+ // (PR monitors, dev servers, subagent fleets) after either
1042+ // lands. The decider rejects settling a starting/running
1043+ // session, so for settle this only ever stops an idle one; a
1044+ // stopped session-set does not count as activity, so the stop
1045+ // cannot un-settle the thread it follows.
1046+ const parkingCommand =
1047+ normalizedCommand . type === "thread.archive" ||
1048+ normalizedCommand . type === "thread.settle"
1049+ ? normalizedCommand
1050+ : undefined ;
1051+ // Best-effort on purpose: the user's archive/settle must not
1052+ // fail because this cleanup read blipped, so a failed read
1053+ // logs and skips the stop instead of propagating.
1054+ const shouldStopSessionAfterCommand = parkingCommand
1055+ ? yield * projectionSnapshotQuery . getThreadShellById ( parkingCommand . threadId ) . pipe (
1056+ Effect . map (
1057+ Option . match ( {
1058+ onNone : ( ) => false ,
1059+ onSome : ( thread ) =>
1060+ thread . session !== null && thread . session . status !== "stopped" ,
1061+ } ) ,
1062+ ) ,
1063+ Effect . catchCause ( ( cause ) =>
1064+ Effect . logWarning (
1065+ "failed to read thread session state before session-stop check" ,
1066+ { threadId : parkingCommand . threadId , cause } ,
1067+ ) . pipe ( Effect . as ( false ) ) ,
1068+ ) ,
1069+ )
1070+ : false ;
10541071 const result = yield * dispatchNormalizedCommand ( normalizedCommand ) ;
1055- if ( normalizedCommand . type === "thread.archive" ) {
1056- if ( shouldStopSessionAfterArchive ) {
1072+ if ( parkingCommand ) {
1073+ const parkingKind = parkingCommand . type === "thread.archive" ? "archive" : "settle" ;
1074+ if ( shouldStopSessionAfterCommand ) {
10571075 yield * Effect . gen ( function * ( ) {
10581076 const stopCommand = yield * normalizeDispatchCommand ( {
10591077 type : "thread.session.stop" ,
10601078 commandId : CommandId . make (
1061- `session-stop-for-archive :${ normalizedCommand . commandId } ` ,
1079+ `session-stop-for-${ parkingKind } :${ parkingCommand . commandId } ` ,
10621080 ) ,
1063- threadId : normalizedCommand . threadId ,
1081+ threadId : parkingCommand . threadId ,
10641082 createdAt : yield * nowIso ,
1083+ // A settled thread can be re-engaged before this stop is
1084+ // decided; the decider then drops the stop instead of
1085+ // killing the new session. Archive stops stay
1086+ // unconditional: turn starts on archived threads are
1087+ // rejected, so there is no new session to protect.
1088+ ...( parkingKind === "settle" ? { onlyIfSettled : true } : { } ) ,
10651089 } ) ;
10661090
10671091 yield * dispatchNormalizedCommand ( stopCommand ) ;
10681092 } ) . pipe (
10691093 Effect . catchCause ( ( cause ) =>
1070- Effect . logWarning ( " failed to stop provider session during archive" , {
1071- threadId : normalizedCommand . threadId ,
1094+ Effect . logWarning ( ` failed to stop provider session during ${ parkingKind } ` , {
1095+ threadId : parkingCommand . threadId ,
10721096 cause,
10731097 } ) ,
10741098 ) ,
10751099 ) ;
10761100 }
10771101
1078- yield * terminalManager . close ( { threadId : normalizedCommand . threadId } ) . pipe (
1079- Effect . catch ( ( error ) =>
1080- Effect . logWarning ( "failed to close thread terminals after archive" , {
1081- threadId : normalizedCommand . threadId ,
1082- error : error . message ,
1083- } ) ,
1084- ) ,
1085- ) ;
1102+ // Terminals are user-opened panes, not thread background
1103+ // work: archive removes the thread from view so they close
1104+ // with it, but a settled thread stays reachable and may be
1105+ // un-settled, so its terminals stay up.
1106+ if ( parkingCommand . type === "thread.archive" ) {
1107+ yield * terminalManager . close ( { threadId : parkingCommand . threadId } ) . pipe (
1108+ Effect . catch ( ( error ) =>
1109+ Effect . logWarning ( "failed to close thread terminals after archive" , {
1110+ threadId : parkingCommand . threadId ,
1111+ error : error . message ,
1112+ } ) ,
1113+ ) ,
1114+ ) ;
1115+ }
10861116 }
10871117 return result ;
10881118 } ) . pipe (
0 commit comments