@@ -24,6 +24,7 @@ import type {
2424 PtyExitEvent ,
2525 PtyCreateArgs ,
2626 PtyCreateResult ,
27+ PtyDisposeResult ,
2728 PtyResumeSessionArgs ,
2829 PtyResumeSessionResult ,
2930 PtySendToSessionArgs ,
@@ -4690,6 +4691,10 @@ export function createPtyService({
46904691 const runningWithoutReachablePty = ! live
46914692 && row . status === "running"
46924693 && ! isPersistedChatToolType ( row . toolType ?? null ) ;
4694+ const idlePersistedChatRuntime = ! live
4695+ && row . status === "running"
4696+ && isPersistedChatToolType ( row . toolType ?? null )
4697+ && computeRuntimeState ( row . id , row . status ) === "running" ;
46934698 const isDetachedFromThisRuntime = ownedByLivePeer || runningWithoutReachablePty ;
46944699 const fallbackStatus = live ? "running" : row . status ;
46954700 return {
@@ -4707,22 +4712,26 @@ export function createPtyService({
47074712 status : "detached" as const ,
47084713 }
47094714 : { } ) ,
4710- runtimeState : isDetachedFromThisRuntime ? "exited" : computeRuntimeState ( row . id , fallbackStatus ) ,
4715+ runtimeState : isDetachedFromThisRuntime
4716+ ? "exited"
4717+ : idlePersistedChatRuntime
4718+ ? "idle"
4719+ : computeRuntimeState ( row . id , fallbackStatus ) ,
47114720 chatSessionId : live
47124721 ? terminalChatSessions . get ( row . id ) ?? live [ 1 ] . chatSessionId ?? row . chatSessionId ?? null
47134722 : terminalChatSessions . get ( row . id ) ?? row . chatSessionId ?? null ,
47144723 } ;
47154724 } ) ;
47164725 } ,
47174726
4718- dispose ( { ptyId, sessionId } : { ptyId : string ; sessionId ?: string } ) : void {
4727+ dispose ( { ptyId, sessionId } : { ptyId : string ; sessionId ?: string } ) : PtyDisposeResult {
47194728 const entry = ptys . get ( ptyId ) ;
47204729 if ( ! entry ) {
4721- if ( ! sessionId ) return ;
4730+ if ( ! sessionId ) return { disposed : false , reason : "missing" } ;
47224731 const session = sessionService . get ( sessionId ) ;
4723- if ( ! session ) return ;
4724- if ( session . status && session . status !== "running" ) return ;
4725- if ( session . ptyId && session . ptyId !== ptyId ) return ;
4732+ if ( ! session ) return { disposed : false , reason : "missing" } ;
4733+ if ( session . status && session . status !== "running" ) return { disposed : false , reason : "not-running" } ;
4734+ if ( session . ptyId && session . ptyId !== ptyId ) return { disposed : false , reason : "session-mismatch" } ;
47264735 if (
47274736 ownerPid != null
47284737 && session . ownerPid != null
@@ -4735,7 +4744,7 @@ export function createPtyService({
47354744 ownerPid : session . ownerPid ,
47364745 currentPid : ownerPid ,
47374746 } ) ;
4738- return ;
4747+ return { disposed : false , reason : "owned-by-peer" } ;
47394748 }
47404749 // The renderer can outlive the pty map (for example after app restart). Allow closing by session id
47414750 // so stale sessions do not get stuck in a "running" state forever.
@@ -4766,9 +4775,12 @@ export function createPtyService({
47664775 }
47674776 }
47684777 logger . warn ( "pty.dispose_orphaned" , { ptyId, sessionId } ) ;
4769- return ;
4778+ return { disposed : true , reason : "orphaned" } ;
47704779 }
4771- if ( entry . disposed ) return ;
4780+ if ( sessionId && entry . sessionId !== sessionId ) {
4781+ return { disposed : false , reason : "session-mismatch" } ;
4782+ }
4783+ if ( entry . disposed ) return { disposed : false , reason : "already-disposed" } ;
47724784 entry . disposed = true ;
47734785 if ( entry . aiTitleTimer ) {
47744786 clearTimeout ( entry . aiTitleTimer ) ;
@@ -4808,14 +4820,15 @@ export function createPtyService({
48084820 ptys . delete ( ptyId ) ;
48094821
48104822 if ( ! entry . tracked ) {
4811- return ;
4823+ return { disposed : true , reason : "disposed" } ;
48124824 }
48134825
48144826 try {
48154827 onSessionEnded ?.( { laneId : entry . laneId , sessionId : entry . sessionId , exitCode : null } ) ;
48164828 } catch {
48174829 // ignore
48184830 }
4831+ return { disposed : true , reason : "disposed" } ;
48194832 } ,
48204833
48214834 disposeAll ( ) : void {
0 commit comments