@@ -929,6 +929,7 @@ export function createPtyService({
929929 const exitListeners = new Set < PtyExitListener > ( ) ;
930930 const terminalChatSessions = new Map < string , string > ( ) ;
931931 const activeTerminalByChatSession = new Map < string , string > ( ) ;
932+ const activeAuxiliaryTerminalByChatSession = new Map < string , string > ( ) ;
932933 const missingResumeTargetBackfillFailures = new Map < string , { toolType : TerminalToolType | null ; checkedAtMs : number } > ( ) ;
933934 const claudeTitleCaptureKeys = new Set < string > ( ) ;
934935 const resumeRuntimeFlights = new Map < string , Promise < PtyCreateResult > > ( ) ;
@@ -2438,20 +2439,30 @@ export function createPtyService({
24382439 const finalRuntimeState = runtimeFromStatus ( status ) ;
24392440 setRuntimeState ( entry . sessionId , finalRuntimeState , { touch : false } ) ;
24402441 runtimeStates . delete ( entry . sessionId ) ;
2441- if ( entry . chatSessionId && activeTerminalByChatSession . get ( entry . chatSessionId ) === entry . sessionId ) {
2442- const replacement = Array . from ( ptys . values ( ) )
2443- . filter ( ( candidate ) => (
2444- candidate . sessionId !== entry . sessionId
2445- && ! candidate . disposed
2446- && candidate . chatSessionId === entry . chatSessionId
2447- ) )
2448- . sort ( ( a , b ) => b . createdAt - a . createdAt ) [ 0 ] ?? null ;
2442+ if (
2443+ entry . chatSessionId
2444+ && isChatCliRoutingEntry ( entry )
2445+ && activeTerminalByChatSession . get ( entry . chatSessionId ) === entry . sessionId
2446+ ) {
2447+ const replacement = liveChatCliEntriesFor ( entry . chatSessionId ) [ 0 ] ?? null ;
24492448 if ( replacement ) {
24502449 activeTerminalByChatSession . set ( entry . chatSessionId , replacement . sessionId ) ;
24512450 } else {
24522451 activeTerminalByChatSession . delete ( entry . chatSessionId ) ;
24532452 }
24542453 }
2454+ if (
2455+ entry . chatSessionId
2456+ && isAuxiliaryRoutingEntry ( entry )
2457+ && activeAuxiliaryTerminalByChatSession . get ( entry . chatSessionId ) === entry . sessionId
2458+ ) {
2459+ const replacement = liveAuxiliaryEntriesFor ( entry . chatSessionId ) [ 0 ] ?? null ;
2460+ if ( replacement ) {
2461+ activeAuxiliaryTerminalByChatSession . set ( entry . chatSessionId , replacement . sessionId ) ;
2462+ } else {
2463+ activeAuxiliaryTerminalByChatSession . delete ( entry . chatSessionId ) ;
2464+ }
2465+ }
24552466 try {
24562467 onSessionRuntimeSignal ?.( {
24572468 laneId : entry . laneId ,
@@ -2692,6 +2703,89 @@ export function createPtyService({
26922703 Array . from ( ptys . entries ( ) ) . find ( ( [ , entry ] ) => entry . sessionId === sessionId && ! entry . disposed ) ?? null
26932704 ) ;
26942705
2706+ const isChatCliRoutingEntry = ( entry : PtyEntry ) : boolean =>
2707+ isPersistedChatToolType ( entry . toolTypeHint ) ;
2708+
2709+ const isAuxiliaryRoutingEntry = ( entry : PtyEntry ) : boolean =>
2710+ ! isChatCliRoutingEntry ( entry ) ;
2711+
2712+ const liveChatCliEntriesFor = ( chatSessionId : string ) : PtyEntry [ ] =>
2713+ Array . from ( ptys . values ( ) )
2714+ . filter ( ( entry ) => (
2715+ entry . chatSessionId === chatSessionId
2716+ && ! entry . disposed
2717+ && isChatCliRoutingEntry ( entry )
2718+ ) )
2719+ . sort ( ( a , b ) => b . createdAt - a . createdAt ) ;
2720+
2721+ const liveAuxiliaryEntriesFor = ( chatSessionId : string ) : PtyEntry [ ] =>
2722+ Array . from ( ptys . values ( ) )
2723+ . filter ( ( entry ) => (
2724+ entry . chatSessionId === chatSessionId
2725+ && ! entry . disposed
2726+ && isAuxiliaryRoutingEntry ( entry )
2727+ ) )
2728+ . sort ( ( a , b ) => b . createdAt - a . createdAt ) ;
2729+
2730+ const activeEntryFromMap = (
2731+ map : Map < string , string > ,
2732+ chatSessionId : string ,
2733+ predicate : ( entry : PtyEntry ) => boolean ,
2734+ ) : PtyEntry | null => {
2735+ const sessionId = map . get ( chatSessionId ) ?? null ;
2736+ if ( ! sessionId ) return null ;
2737+ const live = liveEntryBySessionId ( sessionId ) ;
2738+ if ( live && live [ 1 ] . chatSessionId === chatSessionId && predicate ( live [ 1 ] ) ) return live [ 1 ] ;
2739+ map . delete ( chatSessionId ) ;
2740+ return null ;
2741+ } ;
2742+
2743+ const activeChatCliEntryFor = ( chatSessionId : string ) : PtyEntry | null => {
2744+ const active = activeEntryFromMap ( activeTerminalByChatSession , chatSessionId , isChatCliRoutingEntry ) ;
2745+ if ( active ) return active ;
2746+ const replacement = liveChatCliEntriesFor ( chatSessionId ) [ 0 ] ?? null ;
2747+ if ( replacement ) activeTerminalByChatSession . set ( chatSessionId , replacement . sessionId ) ;
2748+ return replacement ;
2749+ } ;
2750+
2751+ const activeAuxiliaryEntryFor = ( chatSessionId : string ) : PtyEntry | null => {
2752+ const active = activeEntryFromMap ( activeAuxiliaryTerminalByChatSession , chatSessionId , isAuxiliaryRoutingEntry ) ;
2753+ if ( active ) return active ;
2754+ const replacement = liveAuxiliaryEntriesFor ( chatSessionId ) [ 0 ] ?? null ;
2755+ if ( replacement ) activeAuxiliaryTerminalByChatSession . set ( chatSessionId , replacement . sessionId ) ;
2756+ return replacement ;
2757+ } ;
2758+
2759+ const promoteActiveChatCliTerminal = (
2760+ chatSessionId : string ,
2761+ sessionId : string ,
2762+ toolType : TerminalToolType | null ,
2763+ ) : void => {
2764+ if ( ! isPersistedChatToolType ( toolType ) ) return ;
2765+ activeTerminalByChatSession . set ( chatSessionId , sessionId ) ;
2766+ } ;
2767+
2768+ const promoteActiveAuxiliaryTerminal = (
2769+ chatSessionId : string ,
2770+ sessionId : string ,
2771+ toolType : TerminalToolType | null ,
2772+ ) : void => {
2773+ if ( isPersistedChatToolType ( toolType ) ) return ;
2774+ activeAuxiliaryTerminalByChatSession . set ( chatSessionId , sessionId ) ;
2775+ } ;
2776+
2777+ const promoteActiveChatTerminal = (
2778+ chatSessionId : string ,
2779+ sessionId : string ,
2780+ toolType : TerminalToolType | null ,
2781+ ) : void => {
2782+ if ( isPersistedChatToolType ( toolType ) ) {
2783+ promoteActiveChatCliTerminal ( chatSessionId , sessionId , toolType ) ;
2784+ } else {
2785+ promoteActiveAuxiliaryTerminal ( chatSessionId , sessionId , toolType ) ;
2786+ }
2787+ } ;
2788+
26952789 const codexReadyRegion = ( text : string ) : string => {
26962790 const lastPrompt = text . lastIndexOf ( "›" ) ;
26972791 if ( lastPrompt < 0 ) return text ;
@@ -2858,8 +2952,15 @@ export function createPtyService({
28582952 ?? live ?. [ 1 ] . chatSessionId
28592953 ?? summary . chatSessionId
28602954 ?? null ;
2861- const activeTerminalId = chatSessionId ? activeTerminalByChatSession . get ( chatSessionId ) ?? null : null ;
28622955 const fallbackStatus = live ? "running" : summary . status ;
2956+ let active = false ;
2957+ if ( chatSessionId ) {
2958+ if ( isPersistedChatToolType ( summary . toolType ) ) {
2959+ active = activeChatCliEntryFor ( chatSessionId ) ?. sessionId === summary . id ;
2960+ } else {
2961+ active = activeAuxiliaryEntryFor ( chatSessionId ) ?. sessionId === summary . id ;
2962+ }
2963+ }
28632964 return {
28642965 terminalId : summary . id ,
28652966 ptyId : live ?. [ 0 ] ?? summary . ptyId ?? null ,
@@ -2871,7 +2972,7 @@ export function createPtyService({
28712972 goal : summary . goal ,
28722973 status : fallbackStatus ,
28732974 runtimeState : computeRuntimeState ( summary . id , fallbackStatus ) ,
2874- active : Boolean ( activeTerminalId && activeTerminalId === summary . id ) ,
2975+ active,
28752976 startedAt : summary . startedAt ,
28762977 endedAt : live ? null : summary . endedAt ,
28772978 exitCode : live ? null : summary . exitCode ,
@@ -2891,17 +2992,8 @@ export function createPtyService({
28912992 if ( terminalId ) return terminalId ;
28922993 const chatSessionId = cleanOptionalId ( args . chatSessionId ) ;
28932994 if ( ! chatSessionId ) return null ;
2894- const activeTerminalId = activeTerminalByChatSession . get ( chatSessionId ) ?? null ;
2895- if ( activeTerminalId && liveEntryBySessionId ( activeTerminalId ) ) return activeTerminalId ;
2896- const replacement = Array . from ( ptys . values ( ) )
2897- . filter ( ( entry ) => entry . chatSessionId === chatSessionId && ! entry . disposed )
2898- . sort ( ( a , b ) => b . createdAt - a . createdAt ) [ 0 ] ?? null ;
2899- if ( replacement ) {
2900- activeTerminalByChatSession . set ( chatSessionId , replacement . sessionId ) ;
2901- return replacement . sessionId ;
2902- }
2903- activeTerminalByChatSession . delete ( chatSessionId ) ;
2904- return null ;
2995+ // Auxiliary terminals (shell, App Control, etc.) — never route chat-CLI rows.
2996+ return activeAuxiliaryEntryFor ( chatSessionId ) ?. sessionId ?? null ;
29052997 } ;
29062998
29072999 const service = {
@@ -2963,7 +3055,7 @@ export function createPtyService({
29633055 if ( chatSessionId ) {
29643056 attachedEntry . chatSessionId = chatSessionId ;
29653057 terminalChatSessions . set ( existingSession . id , chatSessionId ) ;
2966- activeTerminalByChatSession . set ( chatSessionId , existingSession . id ) ;
3058+ promoteActiveChatTerminal ( chatSessionId , existingSession . id , attachedEntry . toolTypeHint ) ;
29673059 if ( existingSession . chatSessionId !== chatSessionId ) {
29683060 try { sessionService . setChatSessionId ( existingSession . id , chatSessionId ) ; } catch { }
29693061 }
@@ -3262,7 +3354,7 @@ export function createPtyService({
32623354 ptys . set ( ptyId , entry ) ;
32633355 if ( chatSessionId ) {
32643356 terminalChatSessions . set ( sessionId , chatSessionId ) ;
3265- activeTerminalByChatSession . set ( chatSessionId , sessionId ) ;
3357+ promoteActiveChatTerminal ( chatSessionId , sessionId , toolTypeHint ) ;
32663358 if ( existingSession && existingSession . chatSessionId !== chatSessionId ) {
32673359 try { sessionService . setChatSessionId ( sessionId , chatSessionId ) ; } catch { }
32683360 }
@@ -3804,20 +3896,17 @@ export function createPtyService({
38043896 activeForChat ( args : ChatTerminalActiveForChatArgs ) : ChatTerminalSession | null {
38053897 const chatSessionId = cleanOptionalId ( args . chatSessionId ) ;
38063898 if ( ! chatSessionId ) return null ;
3807- const terminalId = activeTerminalByChatSession . get ( chatSessionId ) ?? null ;
3808- if ( terminalId && liveEntryBySessionId ( terminalId ) ) {
3809- const session = sessionService . get ( terminalId ) ;
3810- return session ? terminalSessionFromSummary ( session ) : null ;
3811- }
3812- const replacement = Array . from ( ptys . values ( ) )
3813- . filter ( ( entry ) => entry . chatSessionId === chatSessionId && ! entry . disposed )
3814- . sort ( ( a , b ) => b . createdAt - a . createdAt ) [ 0 ] ?? null ;
3815- if ( ! replacement ) {
3816- activeTerminalByChatSession . delete ( chatSessionId ) ;
3817- return null ;
3899+ const chatCli = activeChatCliEntryFor ( chatSessionId ) ;
3900+ if ( chatCli ) {
3901+ const session = sessionService . get ( chatCli . sessionId ) ;
3902+ if ( session ) {
3903+ promoteActiveChatCliTerminal ( chatSessionId , chatCli . sessionId , session . toolType ) ;
3904+ return terminalSessionFromSummary ( session ) ;
3905+ }
38183906 }
3819- activeTerminalByChatSession . set ( chatSessionId , replacement . sessionId ) ;
3820- const session = sessionService . get ( replacement . sessionId ) ;
3907+ const auxiliary = activeAuxiliaryEntryFor ( chatSessionId ) ;
3908+ if ( ! auxiliary ) return null ;
3909+ const session = sessionService . get ( auxiliary . sessionId ) ;
38213910 return session ? terminalSessionFromSummary ( session ) : null ;
38223911 } ,
38233912
@@ -3827,12 +3916,13 @@ export function createPtyService({
38273916
38283917 // Fast path: an existing live PTY is already bound. Skip the dedup map to
38293918 // keep the no-op cost low.
3830- const activeTerminalId = activeTerminalByChatSession . get ( chatSessionId ) ?? null ;
3831- if ( activeTerminalId ) {
3832- const liveActive = liveEntryBySessionId ( activeTerminalId ) ;
3919+ const liveChatCli = activeChatCliEntryFor ( chatSessionId ) ;
3920+ if ( liveChatCli ) {
3921+ const liveActive = liveEntryBySessionId ( liveChatCli . sessionId ) ;
38333922 if ( liveActive ) {
3923+ promoteActiveChatCliTerminal ( chatSessionId , liveChatCli . sessionId , liveChatCli . toolTypeHint ) ;
38343924 return {
3835- terminalId : activeTerminalId ,
3925+ terminalId : liveChatCli . sessionId ,
38363926 ptyId : liveActive [ 0 ] ,
38373927 pid : liveActive [ 1 ] . pty . pid ?? null ,
38383928 relaunched : false ,
0 commit comments